types: preserve caller types in imported composite generic arguments
This commit is contained in:
1 parent
efa3d1ce5e
commit
70076fe6f0
7 files changed
+176
-3
No files matched your search
@@ -10,6 +10,8 @@ pub:
|
||||
id int
|
||||
}
|
||||
|
||||
type RowVisitor = fn (Row) int
|
||||
|
||||
fn test_callback_parameter_infers_caller_type() {
|
||||
rows := [Row{
|
||||
id: 2
|
||||
@@ -87,3 +89,56 @@ fn test_receiver_method_callback_keeps_caller_type() {
|
||||
})
|
||||
assert ids == [11]
|
||||
}
|
||||
|
||||
fn test_function_type_generic_argument_keeps_caller_type() {
|
||||
cell := genericmodule.cell_of(fn (row Row) Row {
|
||||
return Row{ id: row.id + 1 }
|
||||
})
|
||||
assert cell.value(Row{ id: 12 }).id == 13
|
||||
other := genericmodule.cell_of(fn (row genericmodule.Row) genericmodule.Row {
|
||||
return row
|
||||
})
|
||||
assert other.value(genericmodule.Row{ name: 'module' }).name == 'module'
|
||||
}
|
||||
|
||||
fn test_channel_type_generic_argument_keeps_caller_type() {
|
||||
values := chan Row{cap: 1}
|
||||
values <- Row{ id: 14 }
|
||||
cell := genericmodule.cell_of(values)
|
||||
row := <-cell.value
|
||||
assert row.id == 14
|
||||
}
|
||||
|
||||
fn test_nested_generic_argument_keeps_caller_type() {
|
||||
inner := genericmodule.cell_of(Row{ id: 15 })
|
||||
outer := genericmodule.cell_of(inner)
|
||||
assert outer.value.value.id == 15
|
||||
}
|
||||
|
||||
fn test_callback_alias_generic_argument_keeps_caller_type() {
|
||||
visit := RowVisitor(fn (row Row) int {
|
||||
return row.id
|
||||
})
|
||||
cell := genericmodule.cell_of(visit)
|
||||
assert cell.value(Row{ id: 16 }) == 16
|
||||
}
|
||||
|
||||
fn test_nested_callback_generic_argument_keeps_caller_type() {
|
||||
cell := genericmodule.cell_of(fn (visit fn (Row) Row, row Row) Row {
|
||||
return visit(row)
|
||||
})
|
||||
row := cell.value(fn (value Row) Row {
|
||||
return Row{ id: value.id + 1 }
|
||||
}, Row{ id: 17 })
|
||||
assert row.id == 18
|
||||
}
|
||||
|
||||
fn test_mutable_callback_generic_argument_keeps_caller_type() {
|
||||
cell := genericmodule.cell_of(fn (mut rows []Row) int {
|
||||
rows << Row{ id: 19 }
|
||||
return rows.len
|
||||
})
|
||||
mut rows := []Row{}
|
||||
assert cell.value(mut rows) == 1
|
||||
assert rows[0].id == 19
|
||||
}
|
||||
@@ -10020,6 +10020,14 @@ fn (t &Transformer) generic_arg_for_call_and_decl_module(arg_id u32, call_module
|
||||
arg_comparison_type_id_10 := arg_id
|
||||
arg_source_id_8 := arg_comparison_type_id_10
|
||||
arg := flat.source_type_name(arg_source_id_8)
|
||||
if call_module in ['', 'main'] {
|
||||
// Lock caller types inside composites before normalization in the callee
|
||||
// module can bind their bare names to its own declarations.
|
||||
locked := t.lock_colliding_main_generic_type_text(arg_id, decl_module)
|
||||
if locked != arg_id {
|
||||
return locked
|
||||
}
|
||||
}
|
||||
if call_module in ['', 'main'] && !arg.contains('.') {
|
||||
if info := t.structs[trimmed_transform_text(arg)] {
|
||||
if info.module in ['', 'main'] {
|
||||
@@ -15843,8 +15851,9 @@ fn (t &Transformer) lock_colliding_main_generic_type_text_bytes(typ string, modu
|
||||
mut changed := false
|
||||
mut locked_params := []string{cap: params.len}
|
||||
for param in params {
|
||||
locked := t.lock_colliding_main_generic_type_text_bytes(param, module_name)
|
||||
if locked != trimmed_transform_text(param) {
|
||||
payload := generic_fn_type_param_mode_payload(param)
|
||||
locked := t.lock_colliding_main_generic_type_text_bytes(payload, module_name)
|
||||
if locked != payload {
|
||||
changed = true
|
||||
}
|
||||
locked_params << locked
|
||||
@@ -16887,6 +16896,31 @@ fn (t &Transformer) canonical_generic_specialization_arg(arg_id u32) u32 {
|
||||
if clean.starts_with('[]') {
|
||||
return flat.source_type_id('[]' + flat.source_type_name(t.canonical_generic_specialization_arg(flat.source_type_id(clean[2..]))))
|
||||
}
|
||||
for prefix in ['chan ', 'thread '] {
|
||||
if clean.starts_with(prefix) {
|
||||
return flat.source_type_id(prefix + flat.source_type_name(t.canonical_generic_specialization_arg(flat.source_type_id(clean[prefix.len..]))))
|
||||
}
|
||||
}
|
||||
if clean.starts_with('fn(') || clean.starts_with('fn (') {
|
||||
params, ret := fn_type_text_parts(clean) or { return clean_id }
|
||||
mut canonical_params := []string{cap: params.len}
|
||||
for param in params {
|
||||
canonical_params << flat.source_type_name(t.canonical_generic_specialization_arg(flat.source_type_id(generic_fn_type_param_mode_payload(param))))
|
||||
}
|
||||
canonical_ret := flat.source_type_name(t.canonical_generic_specialization_arg(flat.source_type_id(ret)))
|
||||
return flat.source_type_id('fn (${canonical_params.join(', ')})' + if canonical_ret.len > 0 {
|
||||
' ' + canonical_ret
|
||||
} else {
|
||||
''
|
||||
})
|
||||
}
|
||||
if clean.starts_with('(') && clean.ends_with(')') && clean.contains(',') {
|
||||
mut parts := []string{}
|
||||
for part in split_generic_args(clean[1..clean.len - 1]) {
|
||||
parts << flat.source_type_name(t.canonical_generic_specialization_arg(flat.source_type_id(part)))
|
||||
}
|
||||
return flat.source_type_id('(' + parts.join(', ') + ')')
|
||||
}
|
||||
// A fixed array can reach a second inference pass through its lowered C-name
|
||||
// spelling (`_3int`). Restore the source spelling before using it as a
|
||||
// specialization key, otherwise the duplicate key emits an invalid `_3int`
|
||||
|
||||
@@ -1386,3 +1386,20 @@ fn test_forwarded_generic_call_keeps_caller_context_identity() {
|
||||
t.substitute_cloned_generic_call_type_args(index, mut children, [flat.source_type_id('Context')])
|
||||
assert t.a.node(children[1]).value_text() == 'main.Context'
|
||||
}
|
||||
|
||||
fn test_composite_generic_arguments_keep_callback_shape_and_modes() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.structs['Row'] = []types.StructField{}
|
||||
tc.structs['callee.Row'] = []types.StructField{}
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.structs['Row'] = StructInfo{}
|
||||
t.structs['callee.Row'] = StructInfo{ module: 'callee' }
|
||||
t.qualified_types.set('Row', 'callee.Row')
|
||||
assert flat.source_type_name(t.generic_arg_for_call_and_decl_module(flat.source_type_id('fn (row Row)'), 'main', 'callee')) == 'fn (main.Row)'
|
||||
assert flat.source_type_name(t.generic_arg_for_call_and_decl_module(flat.source_type_id('fn (mut row Row, callee.Row) Row'), 'main', 'callee')) == 'fn (mut main.Row, callee.Row) main.Row'
|
||||
assert flat.source_type_name(t.canonical_generic_specialization_arg(flat.source_type_id('fn (row callee.Row) callee.Row'))) == 'fn (callee.Row) callee.Row'
|
||||
for spelling in ['fn (Row)', 'fn (mut Row)', 'fn (&Row)', 'chan Row', 'thread Row'] {
|
||||
assert flat.source_type_name(t.canonical_generic_specialization_arg(flat.source_type_id(spelling))) == spelling
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,9 @@ refer to the caller's payload. An explicit pointer payload keeps its declared po
|
||||
Intern new source text with `flat.source_type_id(text)` at a parsing boundary. An AST annotation
|
||||
already carries that ID in `node.typ`. Parsing still observes imports, generic bindings, and the
|
||||
declaration context; identical spelling IDs can resolve to different semantic types.
|
||||
Imported generic signatures retain caller-owned types inside callback signatures, channel
|
||||
payloads, and nested generic arguments. A same-named type declared by the imported module
|
||||
does not replace those caller types.
|
||||
`Type.source_type_id()`, `TypeChecker.type_name(value)`, and `type_name_by_id(id)` return spelling
|
||||
IDs. Resolve bytes with `flat.source_type_name(id)` for diagnostics, spelling syntax, or emission.
|
||||
Builtin spelling constants in `v.flat` avoid repeated interning of scalar names.
|
||||
|
||||
@@ -11430,6 +11430,31 @@ fn (tc &TypeChecker) explicit_generic_concrete_arg_text_bytes(type_arg string) s
|
||||
if clean.starts_with('[]') {
|
||||
return '[]' + tc.explicit_generic_concrete_arg_text_bytes(clean[2..])
|
||||
}
|
||||
for prefix in ['mut ', 'shared ', 'atomic ', 'chan mut ', 'chan ', 'thread '] {
|
||||
if clean.starts_with(prefix) {
|
||||
return prefix + tc.explicit_generic_concrete_arg_text_bytes(clean[prefix.len..])
|
||||
}
|
||||
}
|
||||
if is_fn_type_text(clean) {
|
||||
if params, ret := fn_type_text_parts(clean) {
|
||||
mut qualified_params := []string{cap: params.len}
|
||||
for param in params {
|
||||
qualified_params << tc.explicit_generic_concrete_arg_text_bytes(normalize_fn_type_param_text_preserving_mut(param))
|
||||
}
|
||||
return 'fn (${qualified_params.join(', ')})' + if ret.len > 0 {
|
||||
' ' + tc.explicit_generic_concrete_arg_text_bytes(ret)
|
||||
} else {
|
||||
''
|
||||
}
|
||||
}
|
||||
}
|
||||
if clean.starts_with('(') && clean.ends_with(')') {
|
||||
mut qualified_parts := []string{}
|
||||
for part in split_params(clean[1..clean.len - 1]) {
|
||||
qualified_parts << tc.explicit_generic_concrete_arg_text_bytes(part)
|
||||
}
|
||||
return '(' + qualified_parts.join(', ') + ')'
|
||||
}
|
||||
if clean.starts_with('map[') {
|
||||
close := find_matching_bracket(clean, 3)
|
||||
if close > 3 && close + 1 < clean.len {
|
||||
@@ -11444,6 +11469,14 @@ fn (tc &TypeChecker) explicit_generic_concrete_arg_text_bytes(type_arg string) s
|
||||
return clean[..close + 1] + tc.explicit_generic_concrete_arg_text_bytes(clean[close + 1..])
|
||||
}
|
||||
}
|
||||
base, args, is_generic := generic_type_application_parts(clean)
|
||||
if is_generic {
|
||||
mut qualified_args := []string{cap: args.len}
|
||||
for arg in args {
|
||||
qualified_args << tc.explicit_generic_concrete_arg_text_bytes(arg)
|
||||
}
|
||||
return tc.explicit_generic_concrete_arg_text_bytes(base) + '[' + qualified_args.join(', ') + ']'
|
||||
}
|
||||
if !clean.contains('.') {
|
||||
if tc.source_declares_type_in_scope(clean, tc.cur_file, tc.cur_module) {
|
||||
return if tc.cur_module !in ['', 'main'] {
|
||||
|
||||
@@ -21617,7 +21617,9 @@ fn (tc &TypeChecker) c_generic_struct_arg_name(arg string) string {
|
||||
return fixed
|
||||
}
|
||||
}
|
||||
if clean.contains('.') {
|
||||
// A module separator inside a composite belongs to one of its components.
|
||||
// Shortening the whole argument would turn `fn (mod.Row) mod.Row` into `Row`.
|
||||
if clean.contains('.') && !clean.contains_any(' &?![](),') {
|
||||
short := clean.all_after_last('.')
|
||||
if target_source_id := tc.type_aliases.type_id(clean) {
|
||||
target := flat.source_type_name(target_source_id)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_concrete_generic_arguments_lock_nested_caller_types() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.structs['Row'] = []StructField{}
|
||||
tc.structs['foreign.Row'] = []StructField{}
|
||||
assert tc.explicit_generic_concrete_arg_text_bytes('fn (row Row) Row') == 'fn (main.Row) main.Row'
|
||||
assert tc.explicit_generic_concrete_arg_text_bytes('fn (mut row Row) (Row, []Row)') == 'fn (mut main.Row) (main.Row, []main.Row)'
|
||||
assert tc.explicit_generic_concrete_arg_text_bytes('chan Row') == 'chan main.Row'
|
||||
for prefix in ['thread ', 'shared ', 'atomic ', 'chan mut '] {
|
||||
assert tc.explicit_generic_concrete_arg_text_bytes(prefix + 'Row') == prefix + 'main.Row'
|
||||
}
|
||||
assert tc.explicit_generic_concrete_arg_text_bytes('foreign.Cell[Row]') == 'foreign.Cell[main.Row]'
|
||||
assert tc.explicit_generic_concrete_arg_text_bytes('fn (foreign.Row) foreign.Row') == 'fn (foreign.Row) foreign.Row'
|
||||
}
|
||||
|
||||
fn test_generic_struct_c_name_keeps_composite_argument_shape() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.structs['Row'] = []StructField{}
|
||||
tc.structs['foreign.Row'] = []StructField{}
|
||||
for arg in ['fn (foreign.Row) foreign.Row', 'chan foreign.Row', 'foreign.Cell[foreign.Row]'] {
|
||||
assert tc.c_generic_struct_arg_name(arg) == arg
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user