Skip to content

Commit 0997786

Browse files
matzclaude
andcommitted
Convert a hash variant on the way out through a declared return type
A method's C return type comes from its --rbs signature; the expression it answers is typed from observed dataflow. The hash variants are separate C structs, so where ivar writes widen one past what the signature declares, the return handed back an sp_PolyPolyHash * through an sp_StrPolyHash * signature and the build stopped. Nothing in such a program is wrong. The signature is a true description of what the method answers, the widening is spinel's own, and there is no way to write the RBS that avoids it short of dropping the declaration. The assignment side has made this conversion since #4089, through the boxed form the converting entries take. The return side is the same crossing and gets the same conversion. Fixes #4095 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 6f9853f commit 0997786

5 files changed

Lines changed: 75 additions & 0 deletions

File tree

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,12 @@ rbs-seed-test: $(SPINEL) $(RBS_EXTRACT_BIN) $(SP_RT_LIB)
593593
"$$tmp/cp" > "$$tmp/cp.out" 2>/dev/null; \
594594
cmp -s "$$tmp/cp.out" test/rbs-seed/colliding_class_pin.expected || { echo "rbs-seed-test: FAIL (colliding_class_pin output mismatch)"; diff -u test/rbs-seed/colliding_class_pin.expected "$$tmp/cp.out" || true; ok=0; }; \
595595
else echo "rbs-seed-test: FAIL (colliding_class_pin C did not compile)"; ok=0; fi; \
596+
$(SPINEL) test/rbs-seed/return_hash_variant.rb --rbs test/rbs-seed/sig \
597+
-c --no-line-map -o "$$tmp/rh.c" 2>/dev/null; \
598+
if $(CC) -O0 -Ilib $(RBS_SEED_STRICT) "$$tmp/rh.c" $(SP_RT_LIB) $(LDFLAGS) -lm -o "$$tmp/rh" 2>"$$tmp/rh.err"; then \
599+
"$$tmp/rh" > "$$tmp/rh.out" 2>/dev/null; \
600+
cmp -s "$$tmp/rh.out" test/rbs-seed/return_hash_variant.expected || { echo "rbs-seed-test: FAIL (#4095 declared-return hash variant output mismatch)"; diff -u test/rbs-seed/return_hash_variant.expected "$$tmp/rh.out" || true; ok=0; }; \
601+
else echo "rbs-seed-test: FAIL (#4095 declared-return hash variant C did not compile)"; ok=0; fi; \
596602
$(SPINEL) test/rbs-seed/writer_poly_narrowing.rb --rbs test/rbs-seed/sig \
597603
-c --no-line-map -o "$$tmp/wp.c" 2>/dev/null; \
598604
if $(CC) -O0 -Ilib $(RBS_SEED_STRICT) "$$tmp/wp.c" $(SP_RT_LIB) $(LDFLAGS) -lm -o "$$tmp/wp" 2>"$$tmp/wp.err"; then \

src/codegen_stmt.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4856,6 +4856,23 @@ static void emit_tail_value(Compiler *c, int node, Buf *b) {
48564856
return;
48574857
}
48584858
}
4859+
/* A hash variant the body answers where the signature declares another one:
4860+
the variants are separate C structs, so the pointer went back uncoerced
4861+
and the build stopped. An --rbs return type is the usual way the two come
4862+
apart -- the signature is a true description of the method while the ivar
4863+
writes widened its variant -- and the conversion is the one the assignment
4864+
side already makes (#4089), through the boxed form the converting entries
4865+
take. */
4866+
if (ty_is_hash(g_ret_type) && ty_is_hash(comp_ntype(c, node)) &&
4867+
g_ret_type != comp_ntype(c, node) &&
4868+
(g_ret_type == TY_POLY_POLY_HASH || g_ret_type == TY_SYM_POLY_HASH ||
4869+
g_ret_type == TY_STR_POLY_HASH)) {
4870+
const char *hconv = g_ret_type == TY_POLY_POLY_HASH ? "sp_poly_as_poly_poly_hash"
4871+
: g_ret_type == TY_SYM_POLY_HASH ? "sp_poly_as_sym_poly_hash"
4872+
: "sp_poly_as_str_poly_hash";
4873+
buf_printf(b, "%s(", hconv); emit_boxed(c, node, b); buf_puts(b, ")");
4874+
return;
4875+
}
48594876
/* A bare `nil` returned through an int or float slot. emit_expr renders
48604877
NilNode as the numeric default 0, which in those two slots is a real
48614878
value -- the caller reads 0 / 0.0 where the method said nil. Both have a
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{"a" => "b"}
2+
{"a" => "b", "c" => "d"}
3+
{}
4+
{a: 1}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# A method's C return type comes from its --rbs signature while the expression
2+
# it answers is typed from observed dataflow. Where ivar writes widen a hash
3+
# past the signature's variant the two are separate C structs, and the return
4+
# went back uncoerced (#4095). The assignment side already made this
5+
# conversion; the return side did not.
6+
class Jar
7+
def initialize(inbound = {})
8+
@inbound = {}
9+
@out = {}
10+
inbound.each { |k, v| @inbound[k.to_s] = v }
11+
end
12+
13+
def to_h
14+
@inbound.merge(@out)
15+
end
16+
17+
def add(k, v)
18+
@out[k] = v
19+
self
20+
end
21+
end
22+
23+
p Jar.new({ "a" => "b" }).to_h
24+
p Jar.new({ "a" => "b" }).add("c", "d").to_h
25+
p Jar.new.to_h
26+
27+
class SymJar
28+
def initialize
29+
@h = {}
30+
[[:a, 1]].each { |k, v| @h[k] = v }
31+
end
32+
33+
def to_h
34+
@h
35+
end
36+
end
37+
38+
p SymJar.new.to_h
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Jar
2+
def initialize: (?Hash[String, String] inbound) -> void
3+
def to_h: () -> Hash[String, String]
4+
def add: (String k, String v) -> Jar
5+
end
6+
7+
class SymJar
8+
def initialize: () -> void
9+
def to_h: () -> Hash[Symbol, Integer]
10+
end

0 commit comments

Comments
 (0)