From a8cc0bb64eb2e3d48acfa0409c1e95b2226c8e5c Mon Sep 17 00:00:00 2001 From: GGRei Date: Sun, 24 May 2026 20:48:21 +0200 Subject: [PATCH] all: structured generic receiver patterns (#27079) --- doc/docs.md | 70 ++++ vlib/v/ast/table.v | 212 ++++++++-- vlib/v/ast/types.v | 370 ++++++++++++++++++ vlib/v/checker/fn.v | 180 ++++++--- .../tests/check_err_msg_with_generics.out | 354 ----------------- ..._generic_receiver_pattern_mismatch_err.out | 13 + ...d_generic_receiver_pattern_mismatch_err.vv | 28 ++ ...eric_receiver_repeated_placeholder_err.out | 6 + ...neric_receiver_repeated_placeholder_err.vv | 18 + ...tructured_generic_receiver_voidptr_err.out | 6 + ...structured_generic_receiver_voidptr_err.vv | 16 + vlib/v/gen/c/cgen.v | 176 ++++++++- vlib/v/gen/c/fn.v | 280 +++++++++---- vlib/v/parser/fn.v | 15 +- ...generic_receiver_array_init_syntax_err.out | 6 + ..._generic_receiver_array_init_syntax_err.vv | 6 + ...ctured_generic_receiver_declaration_test.v | 305 +++++++++++++++ 17 files changed, 1534 insertions(+), 527 deletions(-) create mode 100644 vlib/v/checker/tests/structured_generic_receiver_pattern_mismatch_err.out create mode 100644 vlib/v/checker/tests/structured_generic_receiver_pattern_mismatch_err.vv create mode 100644 vlib/v/checker/tests/structured_generic_receiver_repeated_placeholder_err.out create mode 100644 vlib/v/checker/tests/structured_generic_receiver_repeated_placeholder_err.vv create mode 100644 vlib/v/checker/tests/structured_generic_receiver_voidptr_err.out create mode 100644 vlib/v/checker/tests/structured_generic_receiver_voidptr_err.vv create mode 100644 vlib/v/parser/tests/structured_generic_receiver_array_init_syntax_err.out create mode 100644 vlib/v/parser/tests/structured_generic_receiver_array_init_syntax_err.vv create mode 100644 vlib/v/tests/generics/structured_generic_receiver_declaration_test.v diff --git a/doc/docs.md b/doc/docs.md index 2df8fcb11..ad028dd6d 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -4716,6 +4716,76 @@ println(compare(1.1, 1.1)) // 0 println(compare(1.1, 1.2)) // -1 ``` +#### Structured generic receiver patterns + +Generic methods can constrain their receiver to a *structured* shape of the +wrapped type, and the checker will bind the inner type parameters from the +concrete receiver. + +The most common case is requiring the wrapped type to be a dynamic array: + +```v +struct Expect[T] { + value T +} + +fn (e Expect[[]T]) first_or(value T) T { + if e.value.len == 0 { + return value + } + return e.value[0] +} + +fn main() { + a := Expect[[]int]{value: [1, 2, 3]} + println(a.first_or(0)) // 1, T is bound to int + + b := Expect[[]string]{value: []string{}} + println(b.first_or('none')) // none, T is bound to string +} +``` + +Nested array patterns are matched recursively, so `Expect[[][]int]` binds +`T = []int`, and `Expect[[]map[string]int]` binds `T = map[string]int`. + +Maps work the same way and can bind two parameters at once: + +```v +struct Expect[T] { + value T +} + +fn (e Expect[map[K]V]) get_or(key K, value V) V { + if key in e.value { + return e.value[key] + } + return value +} + +fn main() { + m := Expect[map[string]int]{value: {'a': 1}} + println(m.get_or('a', 0)) // 1 (K = string, V = int) + println(m.get_or('b', -1)) // -1 +} +``` + +`Expect[map[string]map[string]int]` binds `K = string` and +`V = map[string]int`. + +Rules: + +- `Expect[[]T]` matches dynamic arrays; fixed-size arrays are not matched. +- `Expect[map[K]V]` matches maps. +- Nested patterns are matched recursively. +- Plain generic receivers like `Expect[T]` continue to work, and an exact + concrete receiver method (for example `fn (e Expect[[]int]) ...`) keeps + precedence over a structured pattern. +- A repeated placeholder must bind consistently: `Expect[map[K]K]` on + `Expect[map[string]int]` is rejected because `K` cannot be both `string` + and `int`. +- Raw `voidptr` is not allowed to bind into these patterns; cast through an + explicit V type instead. + ## Concurrency ### Spawning Concurrent Tasks diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index ef6f94952..2ed29ba0a 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -65,31 +65,32 @@ pub struct Table { mut: parsing_type string // name of the type to enable recursive type parsing pub mut: - type_symbols []&TypeSymbol - type_idxs map[string]int - fns map[string]Fn - iface_types map[string][]Type - dumps map[int]string // needed for efficiently generating all _v_dump_expr_TNAME() functions - imports []string // List of all imports - modules []string // Topologically sorted list of all modules registered by the application - global_scope &Scope = unsafe { nil } - cflags []cflag.CFlag - redefined_fns []string - fn_generic_types map[string][][]Type // for generic functions - interfaces map[int]InterfaceDecl - sumtypes map[int]SumTypeDecl - cmod_prefix string // needed for ast.type_to_str(Type) while vfmt; contains `os.` - is_fmt bool - used_features &UsedFeatures = &UsedFeatures{} // filled in by the builder via markused module, when pref.skip_unused = true; - veb_res_idx_cache int // Cache of `veb.Result` type - veb_ctx_idx_cache int // Cache of `veb.Context` type - panic_handler FnPanicHandler = default_table_panic_handler - panic_userdata voidptr = unsafe { nil } // can be used to pass arbitrary data to panic_handler; - panic_npanics int - cur_fn &FnDecl = unsafe { nil } // previously stored in Checker.cur_fn and Gen.cur_fn - cur_lambda &LambdaExpr = unsafe { nil } // current lambda node - cur_concrete_types []Type // current concrete types, e.g. [int, string] - gostmts int // how many `go` statements there were in the parsed files. + type_symbols []&TypeSymbol + type_idxs map[string]int + fns map[string]Fn + iface_types map[string][]Type + dumps map[int]string // needed for efficiently generating all _v_dump_expr_TNAME() functions + imports []string // List of all imports + modules []string // Topologically sorted list of all modules registered by the application + global_scope &Scope = unsafe { nil } + cflags []cflag.CFlag + redefined_fns []string + fn_generic_types map[string][][]Type // for generic functions + structured_receiver_methods map[string][]Fn + interfaces map[int]InterfaceDecl + sumtypes map[int]SumTypeDecl + cmod_prefix string // needed for ast.type_to_str(Type) while vfmt; contains `os.` + is_fmt bool + used_features &UsedFeatures = &UsedFeatures{} // filled in by the builder via markused module, when pref.skip_unused = true; + veb_res_idx_cache int // Cache of `veb.Result` type + veb_ctx_idx_cache int // Cache of `veb.Context` type + panic_handler FnPanicHandler = default_table_panic_handler + panic_userdata voidptr = unsafe { nil } // can be used to pass arbitrary data to panic_handler; + panic_npanics int + cur_fn &FnDecl = unsafe { nil } // previously stored in Checker.cur_fn and Gen.cur_fn + cur_lambda &LambdaExpr = unsafe { nil } // current lambda node + cur_concrete_types []Type // current concrete types, e.g. [int, string] + gostmts int // how many `go` statements there were in the parsed files. // When table.gostmts > 0, __VTHREADS__ is defined, which can be checked with `$if threads {` enum_decls map[string]EnumDecl vls_info map[string]VlsInfo @@ -390,6 +391,21 @@ pub fn (mut t TypeSymbol) register_method(new_fn Fn) int { return t.methods.len - 1 } +fn receiver_pattern_method_key(parent_idx int, name string) string { + return '${parent_idx}.${name}' +} + +pub fn (mut t Table) register_structured_receiver_method(new_fn Fn) { + if !t.receiver_type_is_structured_generic_pattern(new_fn.receiver_type) { + return + } + receiver := t.receiver_generic_types(new_fn.receiver_type) or { return } + key := receiver_pattern_method_key(receiver.parent_idx, new_fn.name) + mut methods := t.structured_receiver_methods[key] or { []Fn{} } + methods << new_fn + t.structured_receiver_methods[key] = methods +} + pub fn (mut t TypeSymbol) update_method(f Fn) int { for i, m in t.methods { if m.name == f.name { @@ -4085,6 +4101,14 @@ fn (mut t Table) should_auto_register_concrete_method(method Fn, parent_type Typ if parent_idx == 0 || method.generic_names.len != concrete_types.len { return false } + for concrete_type in concrete_types { + if t.concrete_type_has_unresolved_generic_parts(concrete_type) { + return false + } + } + if t.receiver_type_is_structured_generic_pattern(method.receiver_type) { + return false + } for i in 1 .. method.params.len { param := method.params[i] mut param_typ := param.typ @@ -4112,6 +4136,144 @@ fn (mut t Table) should_auto_register_concrete_method(method Fn, parent_type Typ return !t.type_contains_transformed_parent_inst(return_type, parent_idx, concrete_types) } +fn (t &Table) concrete_type_has_unresolved_generic_parts(typ Type) bool { + if typ == 0 { + return false + } + if typ.has_flag(.generic) { + return true + } + idx := typ.idx() + if idx <= nil_type_idx { + return false + } + sym := t.sym(typ) + if sym.kind == .placeholder || (sym.kind == .any && !sym.is_builtin()) { + return true + } + match sym.info { + Array { + return t.concrete_type_has_unresolved_generic_parts(sym.info.elem_type) + } + ArrayFixed { + return t.concrete_type_has_unresolved_generic_parts(sym.info.elem_type) + } + Chan { + return t.concrete_type_has_unresolved_generic_parts(sym.info.elem_type) + } + Thread { + return t.concrete_type_has_unresolved_generic_parts(sym.info.return_type) + } + Map { + if t.concrete_type_has_unresolved_generic_parts(sym.info.key_type) { + return true + } + return t.concrete_type_has_unresolved_generic_parts(sym.info.value_type) + } + FnType { + if t.concrete_type_has_unresolved_generic_parts(sym.info.func.return_type) { + return true + } + for param in sym.info.func.params { + if t.concrete_type_has_unresolved_generic_parts(param.typ) { + return true + } + } + return false + } + MultiReturn { + for typ_ in sym.info.types { + if t.concrete_type_has_unresolved_generic_parts(typ_) { + return true + } + } + return false + } + Struct { + if sym.info.concrete_types.len > 0 { + for concrete_type in sym.info.concrete_types { + if t.concrete_type_has_unresolved_generic_parts(concrete_type) { + return true + } + } + return false + } + if sym.generic_types.len == sym.info.generic_types.len + && sym.generic_types != sym.info.generic_types { + for generic_type in sym.generic_types { + if t.concrete_type_has_unresolved_generic_parts(generic_type) { + return true + } + } + return false + } + return sym.info.generic_types.len > 0 + } + Interface { + if sym.info.concrete_types.len > 0 { + for concrete_type in sym.info.concrete_types { + if t.concrete_type_has_unresolved_generic_parts(concrete_type) { + return true + } + } + return false + } + if sym.generic_types.len == sym.info.generic_types.len + && sym.generic_types != sym.info.generic_types { + for generic_type in sym.generic_types { + if t.concrete_type_has_unresolved_generic_parts(generic_type) { + return true + } + } + return false + } + return sym.info.is_generic + } + SumType { + if sym.info.concrete_types.len > 0 { + for concrete_type in sym.info.concrete_types { + if t.concrete_type_has_unresolved_generic_parts(concrete_type) { + return true + } + } + return false + } + if sym.generic_types.len == sym.info.generic_types.len + && sym.generic_types != sym.info.generic_types { + for generic_type in sym.generic_types { + if t.concrete_type_has_unresolved_generic_parts(generic_type) { + return true + } + } + return false + } + return sym.info.is_generic + } + GenericInst { + for concrete_type in sym.info.concrete_types { + if t.concrete_type_has_unresolved_generic_parts(concrete_type) { + return true + } + } + return false + } + UnknownTypeInfo { + if sym.name.contains('[') && sym.name.contains(']') { + args := sym.name.all_after('[').trim_right(']').split(',') + for arg in args { + if util.is_generic_type_name(arg.trim_space()) { + return true + } + } + } + return false + } + else { + return false + } + } +} + fn (mut t Table) unwrap_method_types(ts &TypeSymbol, generic_names []string, concrete_types []Type) { mut needs_unwrap_types := []Type{} for method in ts.get_methods() { diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index d6072d4aa..62d5ad5a6 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -2048,6 +2048,373 @@ pub fn (t &TypeSymbol) find_method(name string) ?Fn { return none } +struct ReceiverGenericTypes { + parent_idx int + types []Type +} + +const max_receiver_generic_pattern_depth = 64 + +pub struct StructuredReceiverMethod { +pub: + method Fn + concrete_types []Type +} + +fn (t &Table) receiver_generic_types(typ Type) ?ReceiverGenericTypes { + idx := typ.clear_flag(.generic).idx() + if idx == 0 || idx >= t.type_symbols.len { + return none + } + sym := t.sym(typ) + match sym.info { + Struct { + if sym.info.concrete_types.len > 0 { + parent_idx := if sym.info.parent_type != 0 { + sym.info.parent_type.clear_flag(.generic).idx() + } else if sym.parent_idx != 0 { + sym.parent_idx + } else { + sym.idx + } + return ReceiverGenericTypes{ + parent_idx: parent_idx + types: sym.info.concrete_types.clone() + } + } + if sym.generic_types.len > 0 { + parent_idx := if sym.parent_idx != 0 { sym.parent_idx } else { sym.idx } + return ReceiverGenericTypes{ + parent_idx: parent_idx + types: sym.generic_types.clone() + } + } + if sym.info.generic_types.len > 0 { + return ReceiverGenericTypes{ + parent_idx: sym.idx + types: sym.info.generic_types.clone() + } + } + } + Interface { + if sym.info.concrete_types.len > 0 { + parent_idx := if sym.info.parent_type != 0 { + sym.info.parent_type.clear_flag(.generic).idx() + } else if sym.parent_idx != 0 { + sym.parent_idx + } else { + sym.idx + } + return ReceiverGenericTypes{ + parent_idx: parent_idx + types: sym.info.concrete_types.clone() + } + } + if sym.generic_types.len > 0 { + parent_idx := if sym.parent_idx != 0 { sym.parent_idx } else { sym.idx } + return ReceiverGenericTypes{ + parent_idx: parent_idx + types: sym.generic_types.clone() + } + } + if sym.info.generic_types.len > 0 { + return ReceiverGenericTypes{ + parent_idx: sym.idx + types: sym.info.generic_types.clone() + } + } + } + SumType { + if sym.info.concrete_types.len > 0 { + parent_idx := if sym.info.parent_type != 0 { + sym.info.parent_type.clear_flag(.generic).idx() + } else if sym.parent_idx != 0 { + sym.parent_idx + } else { + sym.idx + } + return ReceiverGenericTypes{ + parent_idx: parent_idx + types: sym.info.concrete_types.clone() + } + } + if sym.generic_types.len > 0 { + parent_idx := if sym.parent_idx != 0 { sym.parent_idx } else { sym.idx } + return ReceiverGenericTypes{ + parent_idx: parent_idx + types: sym.generic_types.clone() + } + } + if sym.info.generic_types.len > 0 { + return ReceiverGenericTypes{ + parent_idx: sym.idx + types: sym.info.generic_types.clone() + } + } + } + GenericInst { + return ReceiverGenericTypes{ + parent_idx: sym.info.parent_idx + types: sym.info.concrete_types.clone() + } + } + else {} + } + + return none +} + +fn (t &Table) actual_receiver_generic_types(typ Type) ?ReceiverGenericTypes { + if receiver := t.receiver_generic_types(typ) { + return receiver + } + sym := t.sym(typ) + if sym.info is Alias { + return t.receiver_generic_types(sym.info.parent_type.derive(typ)) + } + return none +} + +pub fn (t &Table) find_alias_parent_exact_method(typ Type, name string) ?Fn { + sym := t.sym(typ) + if sym.info is Alias { + parent_sym := t.sym(sym.info.parent_type.derive(typ)) + return parent_sym.find_method(name) + } + return none +} + +fn (t &Table) collect_receiver_generic_pattern_type_names(typ Type, mut names []string, depth int) bool { + if typ == 0 || depth > max_receiver_generic_pattern_depth { + return false + } + sym := t.sym(typ) + if typ.has_flag(.generic) && sym.kind == .any { + if sym.name !in names { + names << sym.name + } + return true + } + next_depth := depth + 1 + match sym.info { + Array { + return t.collect_receiver_generic_pattern_type_names(sym.info.elem_type, mut names, + next_depth) + } + Map { + has_key_name := t.collect_receiver_generic_pattern_type_names(sym.info.key_type, mut + names, next_depth) + has_value_name := t.collect_receiver_generic_pattern_type_names(sym.info.value_type, mut + names, next_depth) + return has_key_name || has_value_name + } + else {} + } + + return false +} + +pub fn (t &Table) structured_receiver_generic_pattern_names(typ Type) []string { + receiver := t.receiver_generic_types(typ) or { return []string{} } + mut names := []string{} + mut has_structured_pattern := false + for receiver_type in receiver.types { + sym := t.sym(receiver_type) + match sym.info { + Array, Map { + if t.collect_receiver_generic_pattern_type_names(receiver_type, mut names, 0) { + has_structured_pattern = true + } + } + else { + t.collect_receiver_generic_pattern_type_names(receiver_type, mut names, 0) + } + } + } + if !has_structured_pattern { + return []string{} + } + return names +} + +fn (t &Table) receiver_type_is_structured_generic_pattern(typ Type) bool { + return t.structured_receiver_generic_pattern_names(typ).len > 0 +} + +fn (t &Table) bind_receiver_generic_type(pattern Type, actual Type, generic_names []string, mut bindings []Type) bool { + return t.bind_receiver_generic_type_with_depth(pattern, actual, generic_names, mut bindings, 0) +} + +fn (t &Table) bind_receiver_generic_type_with_depth(pattern Type, actual Type, generic_names []string, mut bindings []Type, depth int) bool { + if depth > max_receiver_generic_pattern_depth { + return false + } + pattern_sym := t.sym(pattern) + if pattern.has_flag(.generic) && pattern_sym.name in generic_names { + if actual.clear_flags() in voidptr_types { + return false + } + idx := generic_names.index(pattern_sym.name) + if idx < 0 || idx >= bindings.len { + return false + } + if bindings[idx] == 0 { + bindings[idx] = actual + return true + } + return bindings[idx] == actual + } + actual_sym := t.sym(actual) + match pattern_sym.info { + Array { + match actual_sym.info { + Array { + return t.bind_receiver_generic_type_with_depth(pattern_sym.info.elem_type, + actual_sym.info.elem_type, generic_names, mut bindings, depth + 1) + } + else { + return false + } + } + } + Map { + match actual_sym.info { + Map { + if !t.bind_receiver_generic_type_with_depth(pattern_sym.info.key_type, + actual_sym.info.key_type, generic_names, mut bindings, depth + 1) { + return false + } + return t.bind_receiver_generic_type_with_depth(pattern_sym.info.value_type, + actual_sym.info.value_type, generic_names, mut bindings, depth + 1) + } + else { + return false + } + } + } + else {} + } + + return pattern == actual +} + +fn (t &Table) infer_generic_receiver_pattern_types(pattern_receiver Type, actual_receiver Type, generic_names []string) ?[]Type { + pattern := t.receiver_generic_types(pattern_receiver)? + actual := t.actual_receiver_generic_types(actual_receiver)? + if pattern.parent_idx != actual.parent_idx || pattern.types.len != actual.types.len { + return none + } + mut bindings := []Type{len: generic_names.len} + for i, pattern_type in pattern.types { + if !t.bind_receiver_generic_type(pattern_type, actual.types[i], generic_names, mut bindings) { + return none + } + } + for binding in bindings { + if binding == 0 { + return none + } + } + return bindings +} + +fn (t &Table) receiver_generic_type_has_voidptr_binding(pattern Type, actual Type, generic_names []string) bool { + return t.receiver_generic_type_has_voidptr_binding_with_depth(pattern, actual, generic_names, 0) +} + +fn (t &Table) receiver_generic_type_has_voidptr_binding_with_depth(pattern Type, actual Type, generic_names []string, depth int) bool { + if depth > max_receiver_generic_pattern_depth { + return false + } + pattern_sym := t.sym(pattern) + if pattern.has_flag(.generic) && pattern_sym.name in generic_names { + return actual.clear_flags() in voidptr_types + } + actual_sym := t.sym(actual) + match pattern_sym.info { + Array { + if actual_sym.info is Array { + return t.receiver_generic_type_has_voidptr_binding_with_depth(pattern_sym.info.elem_type, + actual_sym.info.elem_type, generic_names, depth + 1) + } + } + Map { + match actual_sym.info { + Map { + if t.receiver_generic_type_has_voidptr_binding_with_depth(pattern_sym.info.key_type, + actual_sym.info.key_type, generic_names, depth + 1) + { + return true + } + return t.receiver_generic_type_has_voidptr_binding_with_depth(pattern_sym.info.value_type, + actual_sym.info.value_type, generic_names, depth + 1) + } + else { + return false + } + } + } + else {} + } + + return false +} + +fn (t &Table) receiver_pattern_has_voidptr_binding(pattern_receiver Type, actual_receiver Type, generic_names []string) bool { + pattern := t.receiver_generic_types(pattern_receiver) or { return false } + actual := t.actual_receiver_generic_types(actual_receiver) or { return false } + if pattern.parent_idx != actual.parent_idx || pattern.types.len != actual.types.len { + return false + } + for i, pattern_type in pattern.types { + if t.receiver_generic_type_has_voidptr_binding(pattern_type, actual.types[i], generic_names) { + return true + } + } + return false +} + +pub fn (table &Table) structured_receiver_method_rejects_voidptr(actual_type Type, name string) bool { + actual := table.actual_receiver_generic_types(actual_type) or { return false } + key := receiver_pattern_method_key(actual.parent_idx, name) + methods := table.structured_receiver_methods[key] or { return false } + for method in methods { + receiver_generic_names := + table.structured_receiver_generic_pattern_names(method.receiver_type) + if receiver_generic_names.len > 0 + && table.receiver_pattern_has_voidptr_binding(method.receiver_type, actual_type, receiver_generic_names) { + return true + } + } + return false +} + +pub fn (table &Table) find_structured_receiver_method_with_types(actual_type Type, name string) ?StructuredReceiverMethod { + actual := table.actual_receiver_generic_types(actual_type) or { return none } + key := receiver_pattern_method_key(actual.parent_idx, name) + methods := table.structured_receiver_methods[key] or { return none } + for method in methods { + receiver_generic_names := + table.structured_receiver_generic_pattern_names(method.receiver_type) + if receiver_generic_names.len == 0 || receiver_generic_names.len > method.generic_names.len + || method.generic_names[..receiver_generic_names.len] != receiver_generic_names { + continue + } + concrete_types := table.infer_generic_receiver_pattern_types(method.receiver_type, + actual_type, receiver_generic_names) or { continue } + return StructuredReceiverMethod{ + method: method + concrete_types: concrete_types + } + } + return none +} + +fn (table &Table) find_structured_receiver_method(actual_type Type, name string) ?Fn { + structured_method := table.find_structured_receiver_method_with_types(actual_type, name)? + return structured_method.method +} + fn specialize_method_with_concrete_types(method Fn, generic_names []string, concrete_types []Type) Fn { mut table := global_table mut resolved := method @@ -2106,6 +2473,9 @@ pub fn (t &TypeSymbol) find_method_with_generic_parent(name string) ?Fn { } return m } + if m := table.find_structured_receiver_method(new_type(t.idx), name) { + return m + } if generic_inst_parent_idx != 0 { mut psym := table.sym(new_type(generic_inst_parent_idx)) for { diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 8345c60a8..7d87a0030 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -3162,12 +3162,34 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) mut method := ast.Fn{} mut has_method := false mut is_method_from_embed := false + mut structured_receiver_concrete_types := []ast.Type{} + mut is_structured_receiver_method := false defer { if has_method && node.is_method { c.check_must_use_call_result(node, method, 'method') } } - if m := left_sym.find_method_with_generic_parent(method_name) { + lookup_sym := c.table.sym(left_type) + if structured_method := c.table.find_structured_receiver_method_with_types(left_type, + method_name) + { + if m := lookup_sym.find_method(method_name) { + method = m + has_method = true + } else if m := c.table.find_alias_parent_exact_method(left_type, method_name) { + method = m + has_method = true + } else { + method = structured_method.method + structured_receiver_concrete_types = structured_method.concrete_types.clone() + is_structured_receiver_method = true + has_method = true + } + if lookup_sym.kind == .interface && method.from_embedded_type != 0 { + is_method_from_embed = true + node.from_embed_types = [method.from_embedded_type] + } + } else if m := left_sym.find_method_with_generic_parent(method_name) { method = m has_method = true if left_sym.kind == .interface && m.from_embedded_type != 0 { @@ -3411,7 +3433,9 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) if left_type != ast.void_type { suggestion := util.new_suggestion(method_name, left_sym.methods.map(it.name)) if unknown_method_msg == '' { - if field := c.table.find_field(left_sym, method_name) { + if c.table.structured_receiver_method_rejects_voidptr(left_type, method_name) { + unknown_method_msg = 'method `${left_sym.name}.${method_name}` cannot bind `voidptr` to a generic receiver pattern; cast the receiver to a concrete V type first' + } else if field := c.table.find_field(left_sym, method_name) { unknown_method_msg = 'unknown method `${field.name}` did you mean to access the field with the same name instead?' } else { mut sname := left_sym.symbol_name_except_generic() @@ -3453,74 +3477,96 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) } mut rec_concrete_types := []ast.Type{} mut method_generic_names_len := method.generic_names.len - match rec_sym.info { - ast.Struct, ast.SumType, ast.Interface { - receiver_generic_names := rec_sym.info.generic_types.map(c.table.sym(it).name) - receiver_generics_in_method := receiver_generic_names.len > 0 - && method.generic_names.len >= receiver_generic_names.len - && method.generic_names[..receiver_generic_names.len] == receiver_generic_names - if rec_sym.info.concrete_types.len > 0 { - rec_concrete_types = rec_sym.info.concrete_types.clone() - } - concrete_types_len := node.concrete_types.len - if rec_is_generic && concrete_types_len == 0 && receiver_generics_in_method { - node.concrete_types = rec_sym.info.generic_types - } else if rec_is_generic && concrete_types_len > 0 && receiver_generics_in_method - && method_generic_names_len > concrete_types_len - && rec_sym.info.generic_types.len + concrete_types_len == method_generic_names_len { - t_concrete_types := node.concrete_types.clone() - node.concrete_types = rec_sym.info.generic_types - node.concrete_types << t_concrete_types - } else if !rec_is_generic && rec_sym.info.concrete_types.len > 0 - && receiver_generics_in_method && concrete_types_len > 0 - && rec_sym.info.concrete_types.len + concrete_types_len == method_generic_names_len { - t_concrete_types := node.concrete_types.clone() - node.concrete_types = rec_sym.info.concrete_types - node.concrete_types << t_concrete_types - } else if !rec_is_generic && receiver_generics_in_method && rec_concrete_types.len > 0 - && concrete_types_len == 0 { - node.concrete_types = rec_concrete_types - } - } - ast.GenericInst { - // Concrete generic instance (e.g. Vec2[f64]): resolve from parent struct - parent_sym := c.table.sym(ast.new_type(rec_sym.info.parent_idx)) - match parent_sym.info { - ast.Struct, ast.SumType, ast.Interface { - receiver_generic_names := - parent_sym.info.generic_types.map(c.table.sym(it).name) - receiver_generics_in_method := receiver_generic_names.len > 0 - && method.generic_names.len >= receiver_generic_names.len - && method.generic_names[..receiver_generic_names.len] == receiver_generic_names + mut receiver_generics_in_selected_method := false + if !is_structured_receiver_method { + match rec_sym.info { + ast.Struct, ast.SumType, ast.Interface { + receiver_generic_names := rec_sym.info.generic_types.map(c.table.sym(it).name) + receiver_generics_in_method := receiver_generic_names.len > 0 + && method.generic_names.len >= receiver_generic_names.len + && method.generic_names[..receiver_generic_names.len] == receiver_generic_names + receiver_generics_in_selected_method = receiver_generics_in_method + if rec_sym.info.concrete_types.len > 0 { rec_concrete_types = rec_sym.info.concrete_types.clone() - concrete_types_len := node.concrete_types.len - if !rec_is_generic && receiver_generics_in_method && rec_concrete_types.len > 0 - && concrete_types_len == 0 { - node.concrete_types = rec_concrete_types - } else if !rec_is_generic && receiver_generics_in_method - && rec_concrete_types.len > 0 && concrete_types_len > 0 - && rec_concrete_types.len + concrete_types_len == method_generic_names_len { - t_concrete_types := node.concrete_types.clone() - node.concrete_types = rec_concrete_types - node.concrete_types << t_concrete_types + } + concrete_types_len := node.concrete_types.len + if rec_is_generic && concrete_types_len == 0 && receiver_generics_in_method { + node.concrete_types = rec_sym.info.generic_types + } else if rec_is_generic && concrete_types_len > 0 && receiver_generics_in_method + && method_generic_names_len > concrete_types_len + && rec_sym.info.generic_types.len + concrete_types_len == method_generic_names_len { + t_concrete_types := node.concrete_types.clone() + node.concrete_types = rec_sym.info.generic_types + node.concrete_types << t_concrete_types + } else if !rec_is_generic && rec_sym.info.concrete_types.len > 0 + && receiver_generics_in_method && concrete_types_len > 0 + && rec_sym.info.concrete_types.len + concrete_types_len == method_generic_names_len { + t_concrete_types := node.concrete_types.clone() + node.concrete_types = rec_sym.info.concrete_types + node.concrete_types << t_concrete_types + } else if !rec_is_generic && receiver_generics_in_method + && rec_concrete_types.len > 0 && concrete_types_len == 0 { + node.concrete_types = rec_concrete_types + } + } + ast.GenericInst { + // Concrete generic instance (e.g. Vec2[f64]): resolve from parent struct + parent_sym := c.table.sym(ast.new_type(rec_sym.info.parent_idx)) + match parent_sym.info { + ast.Struct, ast.SumType, ast.Interface { + receiver_generic_names := + parent_sym.info.generic_types.map(c.table.sym(it).name) + receiver_generics_in_method := receiver_generic_names.len > 0 + && method.generic_names.len >= receiver_generic_names.len + && method.generic_names[..receiver_generic_names.len] == receiver_generic_names + receiver_generics_in_selected_method = receiver_generics_in_method + rec_concrete_types = rec_sym.info.concrete_types.clone() + concrete_types_len := node.concrete_types.len + if !rec_is_generic && receiver_generics_in_method + && rec_concrete_types.len > 0 && concrete_types_len == 0 { + node.concrete_types = rec_concrete_types + } else if !rec_is_generic && receiver_generics_in_method + && rec_concrete_types.len > 0 && concrete_types_len > 0 + && rec_concrete_types.len + concrete_types_len == method_generic_names_len { + t_concrete_types := node.concrete_types.clone() + node.concrete_types = rec_concrete_types + node.concrete_types << t_concrete_types + } } + else {} } - else {} } - } - ast.FnType { - if rec_sym.parent_idx > 0 { - parent_sym := c.table.sym(ast.idx_to_type(rec_sym.parent_idx)) - if parent_sym.info is ast.FnType { - rec_concrete_types = rec_sym.generic_types.clone() - if rec_concrete_types.len > 0 - && method_generic_names_len == rec_concrete_types.len { - node.concrete_types = rec_concrete_types + ast.FnType { + if rec_sym.parent_idx > 0 { + parent_sym := c.table.sym(ast.idx_to_type(rec_sym.parent_idx)) + if parent_sym.info is ast.FnType { + rec_concrete_types = rec_sym.generic_types.clone() + if rec_concrete_types.len > 0 + && method_generic_names_len == rec_concrete_types.len { + node.concrete_types = rec_concrete_types + } } } } + else {} + } + } + + if structured_receiver_concrete_types.len > 0 { + rec_concrete_types = structured_receiver_concrete_types.clone() + receiver_generics_in_selected_method = true + concrete_types_len := node.concrete_types.len + if structured_receiver_concrete_types.len == method_generic_names_len { + node.concrete_types = structured_receiver_concrete_types.clone() + } else if structured_receiver_concrete_types.len < method_generic_names_len { + if concrete_types_len == 0 { + node.concrete_types = structured_receiver_concrete_types.clone() + } else if structured_receiver_concrete_types.len + concrete_types_len == method_generic_names_len { + t_concrete_types := node.concrete_types.clone() + node.concrete_types = structured_receiver_concrete_types.clone() + node.concrete_types << t_concrete_types + } } - else {} } mut concrete_types := node.concrete_types.map(c.unwrap_generic(it)) @@ -3626,7 +3672,8 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) param_is_mut = false no_type_promotion = false } - resolved_method_concrete_types := if method_generic_names_len == rec_concrete_types.len { + resolved_method_concrete_types := if receiver_generics_in_selected_method + && method_generic_names_len == rec_concrete_types.len { rec_concrete_types } else { concrete_types @@ -3738,7 +3785,8 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) c.table.register_fn_concrete_types(method.fkey(), concrete_types) } } - method_concrete_types := if method_generic_names_len == rec_concrete_types.len { + method_concrete_types := if receiver_generics_in_selected_method + && method_generic_names_len == rec_concrete_types.len { rec_concrete_types } else { concrete_types @@ -3874,6 +3922,8 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) // if receiver_type is T, then `receiver_concrete_type` is concrete type, otherwise it is the same as `receiver_type`. node.receiver_concrete_type = if is_method_from_embed { node.from_embed_types.last().derive(method.params[0].typ) + } else if is_structured_receiver_method { + left_type.derive(method.params[0].typ).clear_flag(.generic) } else if is_generic { left_type.derive(method.params[0].typ).clear_flag(.generic) } else { @@ -3928,7 +3978,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) node.return_type = ret_type return ret_type } - if method_generic_names_len > 0 { + if method_generic_names_len > 0 && receiver_generics_in_selected_method { if !left_type.has_flag(.generic) { if left_sym.info is ast.Struct { if method_generic_names_len == left_sym.info.concrete_types.len { diff --git a/vlib/v/checker/tests/check_err_msg_with_generics.out b/vlib/v/checker/tests/check_err_msg_with_generics.out index 9107f70ca..a955a3afe 100644 --- a/vlib/v/checker/tests/check_err_msg_with_generics.out +++ b/vlib/v/checker/tests/check_err_msg_with_generics.out @@ -4,357 +4,3 @@ vlib/v/checker/tests/check_err_msg_with_generics.vv:15:10: error: cannot cast st 15 | println(int(typ)) | ~~~~~~~~ 16 | } -vlib/datatypes/bstree.v:69:17: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 67 | // insert give the possibility to insert an element in the BST. - 68 | pub fn (mut bst BSTree[T]) insert(value T) bool { - 69 | if bst.is_empty() { - | ^ - 70 | bst.root = new_root_node(value) - 71 | return true -vlib/datatypes/bstree.v:73:26: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 71 | return true - 72 | } - 73 | return bst.insert_helper(mut bst.root, value) - | ^ - 74 | } - 75 | -vlib/datatypes/bstree.v:78:16: error: cannot use operator `<` with `Result[[]Token, Err[string]]` - 76 | // insert_helper walks the tree and inserts the given node. - 77 | fn (mut bst BSTree[T]) insert_helper(mut node BSTreeNode[T], value T) bool { - 78 | if node.value < value { - | ^ - 79 | if unsafe { node.right != 0 } && node.right.is_init { - 80 | return bst.insert_helper(mut node.right, value) -vlib/datatypes/bstree.v:80:28: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 78 | if node.value < value { - 79 | if unsafe { node.right != 0 } && node.right.is_init { - 80 | return bst.insert_helper(mut node.right, value) - | ^ - 81 | } - 82 | node.right = new_node(node, value) -vlib/datatypes/bstree.v:84:23: error: cannot use operator `>` with `Result[[]Token, Err[string]]` - 82 | node.right = new_node(node, value) - 83 | return true - 84 | } else if node.value > value { - | ^ - 85 | if unsafe { node.left != 0 } && node.left.is_init { - 86 | return bst.insert_helper(mut node.left, value) -vlib/datatypes/bstree.v:86:28: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 84 | } else if node.value > value { - 85 | if unsafe { node.left != 0 } && node.left.is_init { - 86 | return bst.insert_helper(mut node.left, value) - | ^ - 87 | } - 88 | node.left = new_node(node, value) -vlib/datatypes/bstree.v:96:28: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 94 | // contains checks if an element with a given `value` is inside the BST. - 95 | pub fn (bst &BSTree[T]) contains(value T) bool { - 96 | return bst.contains_helper(bst.root, value) - | ^ - 97 | } - 98 | -vlib/datatypes/bstree.v:105:16: error: cannot use operator `<` with `Result[[]Token, Err[string]]` - 103 | return false - 104 | } - 105 | if node.value < value { - | ^ - 106 | return bst.contains_helper(node.right, value) - 107 | } else if node.value > value { -vlib/datatypes/bstree.v:106:29: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 104 | } - 105 | if node.value < value { - 106 | return bst.contains_helper(node.right, value) - | ^ - 107 | } else if node.value > value { - 108 | return bst.contains_helper(node.left, value) -vlib/datatypes/bstree.v:107:23: error: cannot use operator `>` with `Result[[]Token, Err[string]]` - 105 | if node.value < value { - 106 | return bst.contains_helper(node.right, value) - 107 | } else if node.value > value { - | ^ - 108 | return bst.contains_helper(node.left, value) - 109 | } -vlib/datatypes/bstree.v:108:29: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 106 | return bst.contains_helper(node.right, value) - 107 | } else if node.value > value { - 108 | return bst.contains_helper(node.left, value) - | ^ - 109 | } - 110 | assert node.value == value -vlib/datatypes/bstree.v:116:17: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 114 | // remove removes an element with `value` from the BST. - 115 | pub fn (mut bst BSTree[T]) remove(value T) bool { - 116 | if bst.is_empty() { - | ^ - 117 | return false - 118 | } -vlib/datatypes/bstree.v:119:26: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 117 | return false - 118 | } - 119 | return bst.remove_helper(mut bst.root, value, false) - | ^ - 120 | } - 121 | -vlib/datatypes/bstree.v:130:42: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 128 | // In order to remove the element we need to bring up as parent the max of the - 129 | // left sub-tree. - 130 | mut max_node := bst.get_max_from_right(node.left) - | ^ - 131 | node.bind(mut max_node, true) - 132 | } else if unsafe { node.right != 0 } && node.right.is_init { -vlib/datatypes/bstree.v:131:13: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 129 | // left sub-tree. - 130 | mut max_node := bst.get_max_from_right(node.left) - 131 | node.bind(mut max_node, true) - | ^ - 132 | } else if unsafe { node.right != 0 } && node.right.is_init { - 133 | // Bring up the element with the minimum value in the right sub-tree. -vlib/datatypes/bstree.v:134:41: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 132 | } else if unsafe { node.right != 0 } && node.right.is_init { - 133 | // Bring up the element with the minimum value in the right sub-tree. - 134 | mut min_node := bst.get_min_from_left(node.right) - | ^ - 135 | node.bind(mut min_node, false) - 136 | } else { -vlib/datatypes/bstree.v:135:13: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 133 | // Bring up the element with the minimum value in the right sub-tree. - 134 | mut min_node := bst.get_min_from_left(node.right) - 135 | node.bind(mut min_node, false) - | ^ - 136 | } else { - 137 | mut parent := node.parent -vlib/datatypes/bstree.v:148:16: error: cannot use operator `<` with `Result[[]Token, Err[string]]` - 146 | } - 147 | - 148 | if node.value < value { - | ^ - 149 | return bst.remove_helper(mut node.right, value, false) - 150 | } -vlib/datatypes/bstree.v:149:27: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 147 | - 148 | if node.value < value { - 149 | return bst.remove_helper(mut node.right, value, false) - | ^ - 150 | } - 151 | return bst.remove_helper(mut node.left, value, true) -vlib/datatypes/bstree.v:151:26: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 149 | return bst.remove_helper(mut node.right, value, false) - 150 | } - 151 | return bst.remove_helper(mut node.left, value, true) - | ^ - 152 | } - 153 | -vlib/datatypes/bstree.v:163:31: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 161 | return node - 162 | } - 163 | return bst.get_max_from_right(right_node) - | ^ - 164 | } - 165 | -vlib/datatypes/bstree.v:175:30: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 173 | return node - 174 | } - 175 | return bst.get_min_from_left(left_node) - | ^ - 176 | } - 177 | -vlib/datatypes/bstree.v:185:18: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 183 | // in_order_traversal traverses the BST in order, and returns the result as an array. - 184 | pub fn (bst &BSTree[T]) in_order_traversal() []T { - 185 | mut result := []T{} - | ^ - 186 | bst.in_order_traversal_helper(bst.root, mut result) - 187 | return result -vlib/datatypes/bstree.v:186:31: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 184 | pub fn (bst &BSTree[T]) in_order_traversal() []T { - 185 | mut result := []T{} - 186 | bst.in_order_traversal_helper(bst.root, mut result) - | ^ - 187 | return result - 188 | } -vlib/datatypes/bstree.v:195:31: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 193 | return - 194 | } - 195 | bst.in_order_traversal_helper(node.left, mut result) - | ^ - 196 | result << node.value - 197 | bst.in_order_traversal_helper(node.right, mut result) -vlib/datatypes/bstree.v:197:31: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 195 | bst.in_order_traversal_helper(node.left, mut result) - 196 | result << node.value - 197 | bst.in_order_traversal_helper(node.right, mut result) - | ^ - 198 | } - 199 | -vlib/datatypes/bstree.v:202:18: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 200 | // post_order_traversal traverses the BST in post order, and returns the result in an array. - 201 | pub fn (bst &BSTree[T]) post_order_traversal() []T { - 202 | mut result := []T{} - | ^ - 203 | bst.post_order_traversal_helper(bst.root, mut result) - 204 | return result -vlib/datatypes/bstree.v:203:33: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 201 | pub fn (bst &BSTree[T]) post_order_traversal() []T { - 202 | mut result := []T{} - 203 | bst.post_order_traversal_helper(bst.root, mut result) - | ^ - 204 | return result - 205 | } -vlib/datatypes/bstree.v:214:33: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 212 | } - 213 | - 214 | bst.post_order_traversal_helper(node.left, mut result) - | ^ - 215 | bst.post_order_traversal_helper(node.right, mut result) - 216 | result << node.value -vlib/datatypes/bstree.v:215:33: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 213 | - 214 | bst.post_order_traversal_helper(node.left, mut result) - 215 | bst.post_order_traversal_helper(node.right, mut result) - | ^ - 216 | result << node.value - 217 | } -vlib/datatypes/bstree.v:221:18: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 219 | // pre_order_traversal traverses the BST in pre order, and returns the result as an array. - 220 | pub fn (bst &BSTree[T]) pre_order_traversal() []T { - 221 | mut result := []T{} - | ^ - 222 | bst.pre_order_traversal_helper(bst.root, mut result) - 223 | return result -vlib/datatypes/bstree.v:222:32: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 220 | pub fn (bst &BSTree[T]) pre_order_traversal() []T { - 221 | mut result := []T{} - 222 | bst.pre_order_traversal_helper(bst.root, mut result) - | ^ - 223 | return result - 224 | } -vlib/datatypes/bstree.v:233:32: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 231 | } - 232 | result << node.value - 233 | bst.pre_order_traversal_helper(node.left, mut result) - | ^ - 234 | bst.pre_order_traversal_helper(node.right, mut result) - 235 | } -vlib/datatypes/bstree.v:234:32: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 232 | result << node.value - 233 | bst.pre_order_traversal_helper(node.left, mut result) - 234 | bst.pre_order_traversal_helper(node.right, mut result) - | ^ - 235 | } - 236 | -vlib/datatypes/bstree.v:246:16: error: cannot use operator `<` with `Result[[]Token, Err[string]]` - 244 | } - 245 | - 246 | if node.value < value { - | ^ - 247 | return bst.get_node(node.right, value) - 248 | } -vlib/datatypes/bstree.v:247:22: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 245 | - 246 | if node.value < value { - 247 | return bst.get_node(node.right, value) - | ^ - 248 | } - 249 | return bst.get_node(node.left, value) -vlib/datatypes/bstree.v:249:21: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 247 | return bst.get_node(node.right, value) - 248 | } - 249 | return bst.get_node(node.left, value) - | ^ - 250 | } - 251 | -vlib/datatypes/bstree.v:254:17: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 252 | // to_left returns the value of the node to the left of the node with `value` specified if it exists - 253 | pub fn (bst &BSTree[T]) to_left(value T) !T { - 254 | if bst.is_empty() { - | ^ - 255 | return error('BSTree is empty') - 256 | } -vlib/datatypes/bstree.v:257:22: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 255 | return error('BSTree is empty') - 256 | } - 257 | node := bst.get_node(bst.root, value) - | ^ - 258 | if !node.is_init { - 259 | return error('BSTree is not initialized') -vlib/datatypes/bstree.v:270:17: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 268 | // to_right returns the value of the node to the right of the node with `value` specified if it exists - 269 | pub fn (bst &BSTree[T]) to_right(value T) !T { - 270 | if bst.is_empty() { - | ^ - 271 | return error('BSTree is empty') - 272 | } -vlib/datatypes/bstree.v:273:22: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 271 | return error('BSTree is empty') - 272 | } - 273 | node := bst.get_node(bst.root, value) - | ^ - 274 | if !node.is_init { - 275 | return error('BSTree is not initialized') -vlib/datatypes/bstree.v:287:17: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 285 | // Time complexity O(N) if the BST is not balanced - 286 | pub fn (bst &BSTree[T]) max() !T { - 287 | if bst.is_empty() { - | ^ - 288 | return error('BSTree is empty') - 289 | } -vlib/datatypes/bstree.v:290:31: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 288 | return error('BSTree is empty') - 289 | } - 290 | max := bst.get_max_from_right(bst.root) - | ^ - 291 | if !max.is_init { - 292 | return error('BSTree is not initialized') -vlib/datatypes/bstree.v:300:17: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 298 | // Time complexity O(N) if the BST is not balanced. - 299 | pub fn (bst &BSTree[T]) min() !T { - 300 | if bst.is_empty() { - | ^ - 301 | return error('BSTree is empty') - 302 | } -vlib/datatypes/bstree.v:303:30: error: unknown type `Token`. -Did you mean `Ok[[]Token]`? - 301 | return error('BSTree is empty') - 302 | } - 303 | min := bst.get_min_from_left(bst.root) - | ^ - 304 | if !min.is_init { - 305 | return error('BSTree is not initialized') diff --git a/vlib/v/checker/tests/structured_generic_receiver_pattern_mismatch_err.out b/vlib/v/checker/tests/structured_generic_receiver_pattern_mismatch_err.out new file mode 100644 index 000000000..009c936ea --- /dev/null +++ b/vlib/v/checker/tests/structured_generic_receiver_pattern_mismatch_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/structured_generic_receiver_pattern_mismatch_err.vv:24:4: error: unknown method or field: `ReceiverPatternExpect.array_item` + 22 | 'one': 1 + 23 | } + 24 | }.array_item(0) + | ~~~~~~~~~~~~~ + 25 | ReceiverPatternExpect[[]int]{ + 26 | value: [1, 2, 3] +vlib/v/checker/tests/structured_generic_receiver_pattern_mismatch_err.vv:27:4: error: unknown method or field: `ReceiverPatternExpect.map_value` + 25 | ReceiverPatternExpect[[]int]{ + 26 | value: [1, 2, 3] + 27 | }.map_value(0, 0) + | ~~~~~~~~~~~~~~~ + 28 | } diff --git a/vlib/v/checker/tests/structured_generic_receiver_pattern_mismatch_err.vv b/vlib/v/checker/tests/structured_generic_receiver_pattern_mismatch_err.vv new file mode 100644 index 000000000..a9e5cf475 --- /dev/null +++ b/vlib/v/checker/tests/structured_generic_receiver_pattern_mismatch_err.vv @@ -0,0 +1,28 @@ +struct ReceiverPatternExpect[T] { + value T +} + +fn (e ReceiverPatternExpect[[]T]) array_item(value T) T { + if e.value.len == 0 { + return value + } + return e.value[0] +} + +fn (e ReceiverPatternExpect[map[K]V]) map_value(key K, value V) V { + if key in e.value { + return e.value[key] + } + return value +} + +fn main() { + ReceiverPatternExpect[map[string]int]{ + value: { + 'one': 1 + } + }.array_item(0) + ReceiverPatternExpect[[]int]{ + value: [1, 2, 3] + }.map_value(0, 0) +} diff --git a/vlib/v/checker/tests/structured_generic_receiver_repeated_placeholder_err.out b/vlib/v/checker/tests/structured_generic_receiver_repeated_placeholder_err.out new file mode 100644 index 000000000..a4029eeb7 --- /dev/null +++ b/vlib/v/checker/tests/structured_generic_receiver_repeated_placeholder_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/structured_generic_receiver_repeated_placeholder_err.vv:17:4: error: unknown method or field: `ReceiverPatternExpect.same_value` + 15 | 'one': 1 + 16 | } + 17 | }.same_value('one', 'fallback') + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 18 | } diff --git a/vlib/v/checker/tests/structured_generic_receiver_repeated_placeholder_err.vv b/vlib/v/checker/tests/structured_generic_receiver_repeated_placeholder_err.vv new file mode 100644 index 000000000..4f7be8594 --- /dev/null +++ b/vlib/v/checker/tests/structured_generic_receiver_repeated_placeholder_err.vv @@ -0,0 +1,18 @@ +struct ReceiverPatternExpect[T] { + value T +} + +fn (e ReceiverPatternExpect[map[K]K]) same_value(key K, value K) K { + if key in e.value { + return e.value[key] + } + return value +} + +fn main() { + ReceiverPatternExpect[map[string]int]{ + value: { + 'one': 1 + } + }.same_value('one', 'fallback') +} diff --git a/vlib/v/checker/tests/structured_generic_receiver_voidptr_err.out b/vlib/v/checker/tests/structured_generic_receiver_voidptr_err.out new file mode 100644 index 000000000..ef98505e8 --- /dev/null +++ b/vlib/v/checker/tests/structured_generic_receiver_voidptr_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/structured_generic_receiver_voidptr_err.vv:15:4: error: method `ReceiverPatternExpect[[]voidptr].array_item` cannot bind `voidptr` to a generic receiver pattern; cast the receiver to a concrete V type first + 13 | ReceiverPatternExpect[[]voidptr]{ + 14 | value: []voidptr{} + 15 | }.array_item(unsafe { nil }) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + 16 | } diff --git a/vlib/v/checker/tests/structured_generic_receiver_voidptr_err.vv b/vlib/v/checker/tests/structured_generic_receiver_voidptr_err.vv new file mode 100644 index 000000000..948b40d1c --- /dev/null +++ b/vlib/v/checker/tests/structured_generic_receiver_voidptr_err.vv @@ -0,0 +1,16 @@ +struct ReceiverPatternExpect[T] { + value T +} + +fn (e ReceiverPatternExpect[[]T]) array_item(value T) T { + if e.value.len == 0 { + return value + } + return e.value[0] +} + +fn main() { + ReceiverPatternExpect[[]voidptr]{ + value: []voidptr{} + }.array_item(unsafe { nil }) +} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index fec42240c..33967aa7b 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1796,6 +1796,111 @@ fn (mut g Gen) type_has_unresolved_generic_parts_uncached(typ ast.Type) bool { } } +fn (mut g Gen) typedef_type_has_unresolved_generic_placeholder_parts(typ ast.Type) bool { + if typ == 0 { + return false + } + if typ.has_flag(.generic) { + return true + } + idx := typ.idx() + if idx <= ast.nil_type_idx { + return false + } + sym := g.table.sym(typ) + if sym.kind == .placeholder || (sym.kind == .any && !sym.is_builtin()) { + return true + } + match sym.info { + ast.Array { + return g.typedef_type_has_unresolved_generic_placeholder_parts(sym.info.elem_type) + } + ast.ArrayFixed { + return g.typedef_type_has_unresolved_generic_placeholder_parts(sym.info.elem_type) + } + ast.Chan { + return g.typedef_type_has_unresolved_generic_placeholder_parts(sym.info.elem_type) + } + ast.Thread { + return g.typedef_type_has_unresolved_generic_placeholder_parts(sym.info.return_type) + } + ast.Map { + if g.typedef_type_has_unresolved_generic_placeholder_parts(sym.info.key_type) { + return true + } + return g.typedef_type_has_unresolved_generic_placeholder_parts(sym.info.value_type) + } + ast.FnType { + if g.typedef_type_has_unresolved_generic_placeholder_parts(sym.info.func.return_type) { + return true + } + for param in sym.info.func.params { + if g.typedef_type_has_unresolved_generic_placeholder_parts(param.typ) { + return true + } + } + return false + } + ast.MultiReturn { + for typ_ in sym.info.types { + if g.typedef_type_has_unresolved_generic_placeholder_parts(typ_) { + return true + } + } + return false + } + ast.Struct { + for concrete_type in sym.info.concrete_types { + if g.typedef_type_has_unresolved_generic_placeholder_parts(concrete_type) { + return true + } + } + return false + } + ast.Interface { + for concrete_type in sym.info.concrete_types { + if g.typedef_type_has_unresolved_generic_placeholder_parts(concrete_type) { + return true + } + } + return false + } + ast.SumType { + for concrete_type in sym.info.concrete_types { + if g.typedef_type_has_unresolved_generic_placeholder_parts(concrete_type) { + return true + } + } + return false + } + ast.GenericInst { + for concrete_type in sym.info.concrete_types { + if g.typedef_type_has_unresolved_generic_placeholder_parts(concrete_type) { + return true + } + } + return false + } + ast.UnknownTypeInfo { + if util.is_generic_type_name(sym.name) { + return true + } + if sym.name.contains('[') && sym.name.contains(']') { + args := sym.name.all_after('[').trim_right(']').split(',') + for arg in args { + if util.is_generic_type_name(arg.trim_space()) { + return true + } + } + } + return false + } + else { + return false + } + } +} + fn (mut g Gen) result_type_name(t ast.Type) (string, string) { mut base := g.base_type(t) mut styp := '' @@ -2618,7 +2723,14 @@ pub fn (mut g Gen) write_typedef_types() { } match sym.kind { .array { - info := sym.info as ast.Array + info := if sym.info is ast.Array { + sym.info + } else { + continue + } + if g.typedef_type_has_unresolved_generic_placeholder_parts(info.elem_type) { + continue + } elem_sym := g.table.sym(info.elem_type) if !g.pref.no_preludes && elem_sym.kind != .placeholder && !info.elem_type.has_flag(.generic) { @@ -2627,7 +2739,14 @@ pub fn (mut g Gen) write_typedef_types() { } } .array_fixed { - info := sym.info as ast.ArrayFixed + info := if sym.info is ast.ArrayFixed { + sym.info + } else { + continue + } + if g.typedef_type_has_unresolved_generic_placeholder_parts(info.elem_type) { + continue + } base_elem_sym := g.fixed_array_base_elem_sym(info.elem_type) if base_elem_sym.kind != .struct && base_elem_sym.is_builtin() { styp := sym.cname @@ -2672,9 +2791,26 @@ pub fn (mut g Gen) write_typedef_types() { } } .chan { + info := if sym.info is ast.Chan { + sym.info + } else { + continue + } + if g.typedef_type_has_unresolved_generic_placeholder_parts(info.elem_type) { + continue + } g.ensure_chan_type_definition(ast.new_type(sym.idx)) } .map { + info := if sym.info is ast.Map { + sym.info + } else { + continue + } + if g.typedef_type_has_unresolved_generic_placeholder_parts(info.key_type) + || g.typedef_type_has_unresolved_generic_placeholder_parts(info.value_type) { + continue + } g.type_definitions.writeln('typedef map ${sym.cname};') } else { @@ -11112,6 +11248,32 @@ fn (mut g Gen) write_types(symbols []&ast.TypeSymbol) { ast.Struct { if !struct_names[name] && (!g.pref.skip_unused || sym.idx in g.table.used_features.used_syms) { + mut struct_has_unresolved_type := false + for concrete_type in sym.info.concrete_types { + if g.typedef_type_has_unresolved_generic_placeholder_parts(concrete_type) { + struct_has_unresolved_type = true + break + } + } + if !struct_has_unresolved_type { + for field in sym.info.fields { + if g.typedef_type_has_unresolved_generic_placeholder_parts(field.typ) { + struct_has_unresolved_type = true + break + } + } + } + if !struct_has_unresolved_type { + for embed in sym.info.embeds { + if g.typedef_type_has_unresolved_generic_placeholder_parts(embed) { + struct_has_unresolved_type = true + break + } + } + } + if struct_has_unresolved_type { + continue + } // generate field option types for fixed array of option non-builtin types // before the struct declaration opt_fields := @@ -11145,6 +11307,16 @@ fn (mut g Gen) write_types(symbols []&ast.TypeSymbol) { || (g.pref.skip_unused && sym.idx !in g.table.used_features.used_syms) { continue } + mut has_unresolved_concrete_type := false + for concrete_type in sym.info.concrete_types { + if g.typedef_type_has_unresolved_generic_placeholder_parts(concrete_type) { + has_unresolved_concrete_type = true + break + } + } + if has_unresolved_concrete_type { + continue + } parent_sym := g.table.sym(ast.new_type(sym.info.parent_idx)) if parent_sym.info !is ast.Struct { continue diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index c87e60048..5fd151729 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -29,6 +29,28 @@ fn (g &Gen) method_decl_fkey(method ast.Fn) string { return method.fkey() } +fn (g &Gen) receiver_exact_method_for_type(typ ast.Type, method_name string) ?ast.Fn { + if typ == 0 { + return none + } + sym := g.table.sym(typ) + for method in sym.methods { + if method.name == method_name && method.receiver_type == typ { + return method + } + } + if sym.info is ast.Alias { + parent_type := sym.info.parent_type.derive(typ) + parent_sym := g.table.sym(parent_type) + for method in parent_sym.methods { + if method.name == method_name && method.receiver_type == parent_type { + return method + } + } + } + return none +} + fn (g &Gen) generic_parent_method_fkey(sym ast.TypeSymbol, method_name string) string { match sym.info { ast.Struct, ast.Interface, ast.SumType { @@ -150,6 +172,22 @@ fn (g &Gen) prefers_msvc_compatible_code() bool { fn (mut g Gen) node_decl_fkey(node ast.FnDecl) string { if node.is_method && !node.name.contains('_T_') { + if !node.receiver.typ.has_flag(.generic) + && !g.type_has_unresolved_generic_parts(node.receiver.typ) { + if structured_method := g.table.find_structured_receiver_method_with_types(node.receiver.typ, + node.name) + { + if structured_method.method.receiver_type != node.receiver.typ { + if exact_method := g.receiver_exact_method_for_type(node.receiver.typ, + node.name) + { + if g.method_decl_fkey(exact_method) == node.fkey() { + return node.fkey() + } + } + } + } + } receiver_sym := g.table.sym(node.receiver.typ) receiver_has_concrete_generics := match receiver_sym.info { ast.Struct, ast.Interface, ast.SumType { @@ -245,7 +283,7 @@ fn (g &Gen) generic_file_fn_by_fkey(fkey string) ?ast.FnDecl { // find_all_receiver_concrete_types finds all concrete type instantiations // of the receiver's generic type by examining registered concrete types of // sibling methods on the same receiver that have complete type sets. -fn (g &Gen) find_all_receiver_concrete_types(node ast.FnDecl, receiver_generic_count int) [][]ast.Type { +fn (mut g Gen) find_all_receiver_concrete_types(node ast.FnDecl, receiver_generic_count int) [][]ast.Type { receiver_typ_int := int(node.receiver.typ) prefix := '${receiver_typ_int}.' mut result := [][]ast.Type{} @@ -256,7 +294,7 @@ fn (g &Gen) find_all_receiver_concrete_types(node ast.FnDecl, receiver_generic_c continue } for concrete_types in type_sets { - if concrete_types.any(it.has_flag(.generic)) { + if concrete_types.any(it.has_flag(.generic) || g.type_has_unresolved_generic_parts(it)) { continue } // We need sets that have both receiver + method generics @@ -284,7 +322,7 @@ fn (g &Gen) find_all_receiver_concrete_types(node ast.FnDecl, receiver_generic_c || (sym.info.parent_type != 0 && g.table.sym(sym.info.parent_type).name.all_before('<') == receiver_base_name)) { cts := sym.info.concrete_types - if !cts.any(it.has_flag(.generic)) { + if !cts.any(it.has_flag(.generic) || g.type_has_unresolved_generic_parts(it)) { key := cts.str() if key !in seen { seen[key] = true @@ -401,6 +439,18 @@ fn (mut g Gen) receiver_concrete_types_for_type(typ ast.Type) []ast.Type { } } +fn (mut g Gen) method_receiver_generic_names(method ast.Fn) []string { + if method.params.len == 0 { + return []string{} + } + structured_receiver_generic_names := + g.table.structured_receiver_generic_pattern_names(method.params[0].typ) + if structured_receiver_generic_names.len > 0 { + return structured_receiver_generic_names + } + return g.table.generic_type_names(method.params[0].typ) +} + fn (mut g Gen) method_receiver_only_specialization_types(method ast.Fn, concrete_receiver_type ast.Type) []ast.Type { mut receiver_concrete_types := g.receiver_concrete_types_for_type(concrete_receiver_type) if receiver_concrete_types.len == 0 { @@ -782,6 +832,20 @@ fn (mut g Gen) is_used_by_main(node ast.FnDecl) bool { } fkey := g.node_decl_fkey(node) is_used_by_main = g.table.used_features.used_fns[fkey] + if !is_used_by_main && node.is_method && !node.receiver.typ.has_flag(.generic) + && !g.type_has_unresolved_generic_parts(node.receiver.typ) { + if structured_method := g.table.find_structured_receiver_method_with_types(node.receiver.typ, + node.name) + { + if structured_method.method.receiver_type != node.receiver.typ { + if exact_method := g.receiver_exact_method_for_type(node.receiver.typ, + node.name) + { + is_used_by_main = g.method_decl_fkey(exact_method) == node.fkey() + } + } + } + } if !is_used_by_main && g.effective_fn_generic_names(node).len > 0 && g.table.fn_generic_types[fkey].len > 0 { is_used_by_main = true @@ -3302,9 +3366,30 @@ fn (mut g Gen) resolve_receiver_name(node ast.CallExpr, unwrapped_rec_type ast.T } fn (mut g Gen) receiver_generic_call_context(left_type ast.Type, method_name string) ([]ast.Type, ast.Fn) { + mut exact_method := ast.Fn{} + mut exact_method_exists := false + if left_type != 0 { + if method := g.receiver_exact_method_for_type(left_type, method_name) { + exact_method = method + exact_method_exists = true + } + } + mut has_structured_method_for_exact := false + if structured_method := g.table.find_structured_receiver_method_with_types(left_type, + method_name) + { + if exact_method_exists { + has_structured_method_for_exact = true + } else { + return structured_method.concrete_types.map(g.unwrap_generic(it)), structured_method.method + } + } rec_sym := g.table.final_sym(left_type) mut receiver_concrete_types := []ast.Type{} mut parent_method := ast.Fn{} + if has_structured_method_for_exact { + parent_method = exact_method + } match rec_sym.info { ast.Struct, ast.Interface, ast.SumType { if rec_sym.info.concrete_types.len > 0 { @@ -3316,7 +3401,9 @@ fn (mut g Gen) receiver_generic_call_context(left_type ast.Type, method_name str if rec_sym.info.parent_type.has_flag(.generic) { parent_sym := g.table.sym(rec_sym.info.parent_type) if m := parent_sym.find_method(method_name) { - parent_method = m + if !has_structured_method_for_exact { + parent_method = m + } } } } @@ -3325,7 +3412,9 @@ fn (mut g Gen) receiver_generic_call_context(left_type ast.Type, method_name str if rec_sym.info.parent_idx > 0 { parent_sym := g.table.sym(ast.idx_to_type(rec_sym.info.parent_idx)) if m := parent_sym.find_method(method_name) { - parent_method = m + if !has_structured_method_for_exact { + parent_method = m + } } } } @@ -3342,7 +3431,9 @@ fn (mut g Gen) receiver_generic_call_context(left_type ast.Type, method_name str if non_final_sym.info.parent_idx > 0 { parent_sym := g.table.sym(ast.idx_to_type(non_final_sym.info.parent_idx)) if m := parent_sym.find_method(method_name) { - parent_method = m + if !has_structured_method_for_exact { + parent_method = m + } } } } else if non_final_sym.generic_types.len > 0 && non_final_sym.parent_idx > 0 @@ -3352,7 +3443,9 @@ fn (mut g Gen) receiver_generic_call_context(left_type ast.Type, method_name str receiver_concrete_types = non_final_sym.generic_types.map(g.unwrap_generic(it)) parent_sym := g.table.sym(ast.idx_to_type(non_final_sym.parent_idx)) if m := parent_sym.find_method(method_name) { - parent_method = m + if !has_structured_method_for_exact { + parent_method = m + } } } } @@ -4629,6 +4722,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { mut method_generic_names_len := 0 mut receiver_generic_names := []string{} mut receiver_generics_in_method := false + mut receiver_method_concrete_types := []ast.Type{} mut has_method := false mut method_for_generics := ast.Fn{} if left_type != g.unwrap_generic(node.left_type) @@ -4676,12 +4770,8 @@ fn (mut g Gen) method_call(node ast.CallExpr) { receiver_type = receiver_type.derive(method_for_generics.params[0].typ).clear_flag(.generic) receiver_is_mut = method_for_generics.params[0].is_mut - receiver_generic_names = g.table.generic_type_names(method_for_generics.params[0].typ) } method_generic_names_len = method_for_generics.generic_names.len - receiver_generics_in_method = receiver_generic_names.len > 0 - && method_generic_names_len >= receiver_generic_names.len - && method_for_generics.generic_names[..receiver_generic_names.len] == receiver_generic_names if node.concrete_types.len == method_generic_names_len && node.concrete_types.len > 0 { trust_node_concrete_types = node.raw_concrete_types.len == method_generic_names_len && node.raw_concrete_types.len > 0 @@ -4736,76 +4826,97 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } } } - if method_generic_names_len > 0 && g.cur_fn != unsafe { nil } - && g.cur_concrete_types.len > 0 { - mut resolved_arg_types := []ast.Type{} - expected_arg_generic_slots := - int_max(method_generic_names_len - receiver_concrete_types.len, 0) - if expected_arg_generic_slots > 0 { - offset := if method_for_generics.is_method { 1 } else { 0 } - for i, arg in node.args { - param := if method_for_generics.is_variadic - && i >= method_for_generics.params.len - (offset + 1) { - method_for_generics.params.last() - } else { - method_for_generics.params[offset + i] - } - param_infer_typ := g.generic_param_infer_type(param) - if !param_infer_typ.has_flag(.generic) - && !g.type_has_unresolved_generic_parts(param_infer_typ) { - continue - } - param_sym := g.table.sym(param_infer_typ) - if param_sym.name !in method_for_generics.generic_names { - continue - } - if param.typ == method_for_generics.params[0].typ { - continue - } - if arg.expr is ast.Ident && arg.expr.obj is ast.Var - && (arg.expr.obj as ast.Var).ct_type_var == .generic_param { - mut resolved_arg_type := - g.resolve_current_fn_generic_param_type(arg.expr.name) - if (arg.is_mut || (arg.expr.obj as ast.Var).is_mut) - && resolved_arg_type.is_ptr() { - resolved_arg_type = resolved_arg_type.deref() - } - if resolved_arg_type != 0 { - resolved_arg_types << resolved_arg_type - } - } - } - } - if !trust_node_concrete_types && resolved_arg_types.len == expected_arg_generic_slots { - concrete_types = receiver_concrete_types.clone() - concrete_types << resolved_arg_types - concrete_types_from_resolved_args = true - } - } } - if concrete_types.len == 0 && receiver_concrete_types.len > 0 - && (!has_method || method_generic_names_len == 0 || receiver_generics_in_method) { - concrete_types = receiver_concrete_types.clone() - concrete_types_from_receiver = true + method_receiver_names_for_full := g.method_receiver_generic_names(method_for_generics) + parent_receiver_names_for_full := if parent_generic_method.params.len > 0 { + g.method_receiver_generic_names(parent_generic_method) + } else { + []string{} } - full_method_generic_names_len := if parent_method_generic_names_len > method_generic_names_len { + use_parent_generic_method := parent_method_generic_names_len > method_generic_names_len + || (method_receiver_names_for_full.len == 0 && parent_receiver_names_for_full.len > 0) + full_method_generic_names_len := if use_parent_generic_method { parent_method_generic_names_len } else { method_generic_names_len } - full_method := if parent_method_generic_names_len > method_generic_names_len { + full_method := if use_parent_generic_method { parent_generic_method } else { method_for_generics } - full_method_generic_names := if parent_method_generic_names_len > method_generic_names_len { + full_method_generic_names := if use_parent_generic_method { parent_generic_method.generic_names.clone() } else { method_for_generics.generic_names.clone() } + if has_method { + receiver_generic_names = g.method_receiver_generic_names(full_method) + receiver_only_generic_method := receiver_generic_names.len > 0 + && full_method.generic_names.len == 0 + receiver_prefixed_generic_method := receiver_generic_names.len > 0 + && full_method.generic_names.len >= receiver_generic_names.len + && full_method.generic_names[..receiver_generic_names.len] == receiver_generic_names + receiver_generics_in_method = receiver_concrete_types.len >= receiver_generic_names.len + && (receiver_only_generic_method || receiver_prefixed_generic_method) + if receiver_generics_in_method { + receiver_method_concrete_types = + receiver_concrete_types[..receiver_generic_names.len].clone() + } + } + if has_method && method_generic_names_len > 0 && g.cur_fn != unsafe { nil } + && g.cur_concrete_types.len > 0 { + mut resolved_arg_types := []ast.Type{} + expected_arg_generic_slots := + int_max(method_generic_names_len - receiver_method_concrete_types.len, 0) + if expected_arg_generic_slots > 0 { + offset := if method_for_generics.is_method { 1 } else { 0 } + for i, arg in node.args { + param := if method_for_generics.is_variadic + && i >= method_for_generics.params.len - (offset + 1) { + method_for_generics.params.last() + } else { + method_for_generics.params[offset + i] + } + param_infer_typ := g.generic_param_infer_type(param) + if !param_infer_typ.has_flag(.generic) + && !g.type_has_unresolved_generic_parts(param_infer_typ) { + continue + } + param_sym := g.table.sym(param_infer_typ) + if param_sym.name !in method_for_generics.generic_names { + continue + } + if param.typ == method_for_generics.params[0].typ { + continue + } + if arg.expr is ast.Ident && arg.expr.obj is ast.Var + && (arg.expr.obj as ast.Var).ct_type_var == .generic_param { + mut resolved_arg_type := g.resolve_current_fn_generic_param_type(arg.expr.name) + if (arg.is_mut || (arg.expr.obj as ast.Var).is_mut) + && resolved_arg_type.is_ptr() { + resolved_arg_type = resolved_arg_type.deref() + } + if resolved_arg_type != 0 { + resolved_arg_types << resolved_arg_type + } + } + } + } + if !trust_node_concrete_types && resolved_arg_types.len == expected_arg_generic_slots { + concrete_types = receiver_method_concrete_types.clone() + concrete_types << resolved_arg_types + concrete_types_from_resolved_args = true + } + } + if concrete_types.len == 0 && receiver_method_concrete_types.len > 0 + && (!has_method || method_generic_names_len == 0 || receiver_generics_in_method) { + concrete_types = receiver_method_concrete_types.clone() + concrete_types_from_receiver = true + } if has_method { name = g.specialized_method_name_from_receiver_context(full_method, - receiver_concrete_types, parent_generic_method, name) + receiver_method_concrete_types, parent_generic_method, name) } if has_method && full_method_generic_names_len == 0 { concrete_types = []ast.Type{} @@ -4821,15 +4932,16 @@ fn (mut g Gen) method_call(node ast.CallExpr) { if concrete_types.len == 0 && has_method && full_method_generic_names_len > 0 { concrete_types = []ast.Type{len: full_method_generic_names_len, init: ast.void_type} } - if concrete_types.len > 0 && !concrete_types_from_receiver && receiver_concrete_types.len > 0 + if concrete_types.len > 0 && !concrete_types_from_receiver + && receiver_method_concrete_types.len > 0 && full_method_generic_names_len > concrete_types.len - && receiver_concrete_types.len + concrete_types.len == full_method_generic_names_len { + && receiver_method_concrete_types.len + concrete_types.len == full_method_generic_names_len { method_concrete_types := concrete_types.clone() - concrete_types = receiver_concrete_types.clone() + concrete_types = receiver_method_concrete_types.clone() concrete_types << method_concrete_types } - if receiver_concrete_types.len > 0 { - for i, receiver_ct in receiver_concrete_types { + if receiver_method_concrete_types.len > 0 { + for i, receiver_ct in receiver_method_concrete_types { if i < concrete_types.len { concrete_types[i] = receiver_ct } @@ -4844,7 +4956,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } } name_already_specialized := specialized_suffix != '' && name.ends_with(specialized_suffix) - rec_len := receiver_concrete_types.len + rec_len := receiver_method_concrete_types.len if has_method { expected_method_generic_names_len := if full_method_generic_names_len > method_generic_names_len { full_method_generic_names_len @@ -4915,9 +5027,9 @@ fn (mut g Gen) method_call(node ast.CallExpr) { continue } mut explicit_slot := slot - if receiver_concrete_types.len > 0 && node.raw_concrete_types.len > 0 + if receiver_method_concrete_types.len > 0 && node.raw_concrete_types.len > 0 && full_method_generic_names_len > node.raw_concrete_types.len - && receiver_concrete_types.len + node.raw_concrete_types.len == full_method_generic_names_len { + && receiver_method_concrete_types.len + node.raw_concrete_types.len == full_method_generic_names_len { explicit_slot = slot - rec_len } mut explicit_concrete_type := ast.no_type @@ -5020,7 +5132,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { // When the receiver's generics are part of the method's generic params, // arg-resolved concrete types may be stale (scope var types from a // different checker pass). Validate node.concrete_types against - // receiver_concrete_types: if receiver positions match, the checker + // receiver_method_concrete_types: if receiver positions match, the checker // processed this instantiation, so trust the method's own positions. if receiver_generics_in_method && !trust_node_concrete_types && !concrete_types_from_resolved_args @@ -5032,7 +5144,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { break } node_ct := ast.mktyp(g.unwrap_generic(node.concrete_types[i])) - if node_ct != receiver_concrete_types[i] { + if node_ct != receiver_method_concrete_types[i] { receiver_validated = false break } @@ -5050,7 +5162,15 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } } concrete_types = concrete_types.filter(it != ast.void_type) - mut name_fkey := g.resolve_method_decl_fkey_for_type(left_type, method_name) + mut name_fkey := '' + if _ := g.table.find_structured_receiver_method_with_types(left_type, method_name) { + if exact_method := g.receiver_exact_method_for_type(left_type, method_name) { + name_fkey = g.method_decl_fkey(exact_method) + } + } + if name_fkey == '' { + name_fkey = g.resolve_method_decl_fkey_for_type(left_type, method_name) + } if name_fkey == '' { name_fkey = g.method_decl_fkey(full_method) } @@ -5069,7 +5189,8 @@ fn (mut g Gen) method_call(node ast.CallExpr) { expected_name_concrete_types := if recovered_concrete_types.len > 0 { recovered_concrete_types.clone() } else { - g.method_name_concrete_types(name_fkey, concrete_types, receiver_concrete_types) + g.method_name_concrete_types(name_fkey, concrete_types, + receiver_method_concrete_types) } if expected_name_concrete_types.len > 0 && g.generic_fn_name(expected_name_concrete_types, method_name) != raw_method_name { @@ -5083,7 +5204,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } } else { raw_name_concrete_types := g.raw_method_name_concrete_types(raw_method_name, - method_name, node.raw_concrete_types, receiver_concrete_types) + method_name, node.raw_concrete_types, receiver_method_concrete_types) mut placeholder_raw_name_concrete_types := []ast.Type{} if raw_name_concrete_types.len == 0 && node.raw_concrete_types.len > 0 && full_method_generic_names.any(raw_method_name.contains('_T_${it}')) { @@ -5112,7 +5233,8 @@ fn (mut g Gen) method_call(node ast.CallExpr) { || g.type_has_unresolved_generic_parts(it))) { recovered_concrete_types.clone() } else { - g.method_name_concrete_types(name_fkey, concrete_types, receiver_concrete_types) + g.method_name_concrete_types(name_fkey, concrete_types, + receiver_method_concrete_types) } method_suffix := g.generic_fn_name(name_concrete_types, '') method_name_already_specialized = method_suffix != '' diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index fd17857de..06f2beb77 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -840,7 +840,16 @@ fn (mut p Parser) fn_decl() ast.FnDecl { } } fn_generic_names := generic_names.clone() generic_names = p.table.generic_type_names(rec.typ) + mut is_structured_receiver_pattern := false if decl_generic_names.len != generic_names.len { + structured_generic_names := + p.table.structured_receiver_generic_pattern_names(rec.typ) + if structured_generic_names.len > 0 { + generic_names = structured_generic_names.clone() + is_structured_receiver_pattern = true + } + } + if decl_generic_names.len != generic_names.len && !is_structured_receiver_pattern { plural := if decl_generic_names.len == 1 { '' } else { 's' } p.error_with_pos('expected ${decl_generic_names.len} generic parameter${plural}, got ${generic_names.len}', rec.type_pos) @@ -980,7 +989,7 @@ run them via `v file.v` instead', scope: unsafe { nil } } } - type_sym_method_idx = type_sym.register_method(ast.Fn{ + method := ast.Fn{ name: name file_mode: file_mode params: params @@ -1010,7 +1019,9 @@ run them via `v file.v` instead', language: language // is_expand_simple_interpolation: is_expand_simple_interpolation - }) + } + type_sym_method_idx = type_sym.register_method(method) + p.table.register_structured_receiver_method(method) } else { name = match language { .c { 'C.${name}' } diff --git a/vlib/v/parser/tests/structured_generic_receiver_array_init_syntax_err.out b/vlib/v/parser/tests/structured_generic_receiver_array_init_syntax_err.out new file mode 100644 index 000000000..25f045898 --- /dev/null +++ b/vlib/v/parser/tests/structured_generic_receiver_array_init_syntax_err.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/structured_generic_receiver_array_init_syntax_err.vv:2:33: error: unexpected token `}`, expecting `)` + 1 | struct ReceiverPatternExpect[T] {} + 2 | fn (e ReceiverPatternExpect[[]T{}]) item(value T) T { + | ^ + 3 | return value + 4 | } diff --git a/vlib/v/parser/tests/structured_generic_receiver_array_init_syntax_err.vv b/vlib/v/parser/tests/structured_generic_receiver_array_init_syntax_err.vv new file mode 100644 index 000000000..3446ee928 --- /dev/null +++ b/vlib/v/parser/tests/structured_generic_receiver_array_init_syntax_err.vv @@ -0,0 +1,6 @@ +struct ReceiverPatternExpect[T] {} +fn (e ReceiverPatternExpect[[]T{}]) item(value T) T { + return value +} + +fn main() {} diff --git a/vlib/v/tests/generics/structured_generic_receiver_declaration_test.v b/vlib/v/tests/generics/structured_generic_receiver_declaration_test.v new file mode 100644 index 000000000..4d0267a6c --- /dev/null +++ b/vlib/v/tests/generics/structured_generic_receiver_declaration_test.v @@ -0,0 +1,305 @@ +struct ReceiverPatternExpect[T] { + value T +} + +struct MixedReceiverPatternBox[T, U] { + direct T + items U +} + +type IntListExpect = ReceiverPatternExpect[[]int] +type StringIntMapExpect = ReceiverPatternExpect[map[string]int] +type MixedIntStringListBox = MixedReceiverPatternBox[int, []string] + +fn (e ReceiverPatternExpect[T]) broad_receiver_returns_value() T { + return e.value +} + +fn (e ReceiverPatternExpect[[]int]) concrete_array_receiver_returns_len() int { + return e.value.len +} + +fn (e ReceiverPatternExpect[[]int]) same_name_prefers_concrete_receiver() string { + return 'concrete' +} + +fn (e ReceiverPatternExpect[[]int]) same_name_with_method_generic_prefers_concrete_receiver[U](extra U) string { + return 'concrete' +} + +fn (e ReceiverPatternExpect[[]T]) dynamic_array_receiver_pattern_returns_first_or_value(value T) T { + if e.value.len == 0 { + return value + } + return e.value[0] +} + +fn (e ReceiverPatternExpect[[]T]) same_name_prefers_concrete_receiver() string { + return 'pattern' +} + +fn (e ReceiverPatternExpect[[]T]) same_name_with_method_generic_prefers_concrete_receiver[U](extra U) string { + return 'pattern' +} + +fn (e ReceiverPatternExpect[[]T]) dynamic_array_receiver_pattern_pairs_with_method_generic[U](value T, + extra U) (T, U) { + if e.value.len == 0 { + return value, extra + } + return e.value[0], extra +} + +fn (e ReceiverPatternExpect[map[K]V]) map_receiver_pattern_returns_existing_or_default(key K, + value V) V { + if key in e.value { + return e.value[key] + } + return value +} + +fn (e ReceiverPatternExpect[map[K]V]) map_receiver_pattern_pairs_with_method_generic[U](key K, + value V, extra U) (V, U) { + if key in e.value { + return e.value[key], extra + } + return value, extra +} + +fn (e ReceiverPatternExpect[map[K]V]) same_name_map_exact_prefers_concrete[U](extra U) string { + _ = extra + return 'pattern' +} + +fn (e ReceiverPatternExpect[map[string]int]) same_name_map_exact_prefers_concrete[U](extra U) string { + _ = extra + return 'concrete' +} + +fn (e ReceiverPatternExpect[map[K]V]) same_name_map_method() string { + return 'pattern' +} + +fn (e ReceiverPatternExpect[map[string]int]) same_name_map_method() string { + return 'concrete' +} + +fn (b MixedReceiverPatternBox[T, []U]) mixed_receiver_pattern_values_with_method_generic[V](fallback U, + marker V) (T, U, V) { + if b.items.len == 0 { + return b.direct, fallback, marker + } + return b.direct, b.items[0], marker +} + +fn (b MixedReceiverPatternBox[int, []string]) same_name_with_method_generic_prefers_concrete_mixed_receiver[V](marker V) string { + return 'concrete' +} + +fn (b MixedReceiverPatternBox[T, []U]) same_name_with_method_generic_prefers_concrete_mixed_receiver[V](marker V) string { + return 'pattern' +} + +fn test_existing_generic_receiver_declarations_still_work() { + assert ReceiverPatternExpect[int]{ + value: 7 + }.broad_receiver_returns_value() == 7 + assert ReceiverPatternExpect[[]int]{ + value: [1, 2, 3] + }.concrete_array_receiver_returns_len() == 3 +} + +fn test_concrete_receiver_method_takes_precedence_over_structured_pattern() { + assert ReceiverPatternExpect[[]int]{ + value: [1, 2, 3] + }.same_name_prefers_concrete_receiver() == 'concrete' + assert ReceiverPatternExpect[[]string]{ + value: ['alpha'] + }.same_name_prefers_concrete_receiver() == 'pattern' +} + +fn test_concrete_receiver_method_with_method_generic_takes_precedence_over_structured_pattern() { + assert ReceiverPatternExpect[[]int]{ + value: [1, 2, 3] + }.same_name_with_method_generic_prefers_concrete_receiver('extra') == 'concrete' + assert ReceiverPatternExpect[[]int]{ + value: [1] + }.same_name_with_method_generic_prefers_concrete_receiver[string]('extra') == 'concrete' + assert ReceiverPatternExpect[[]string]{ + value: ['alpha'] + }.same_name_with_method_generic_prefers_concrete_receiver('extra') == 'pattern' +} + +fn test_structured_generic_receiver_patterns_can_be_declared() { + assert true +} + +fn test_dynamic_array_receiver_pattern_binds_element_type() { + assert ReceiverPatternExpect[[]int]{ + value: [1, 2, 3] + }.dynamic_array_receiver_pattern_returns_first_or_value(9) == 1 + assert ReceiverPatternExpect[[]string]{ + value: ['alpha', 'beta'] + }.dynamic_array_receiver_pattern_returns_first_or_value('fallback') == 'alpha' +} + +fn test_dynamic_array_receiver_pattern_binds_nested_element_type() { + assert ReceiverPatternExpect[[][]int]{ + value: [[1, 2], [3, 4]] + }.dynamic_array_receiver_pattern_returns_first_or_value([9, 10]) == [1, 2] + assert ReceiverPatternExpect[[]map[string]int]{ + value: [ + { + 'one': 1 + }, + ] + }.dynamic_array_receiver_pattern_returns_first_or_value({ + 'zero': 0 + })['one'] == 1 +} + +fn test_dynamic_array_receiver_pattern_method_generic_is_inferred() { + value, extra := ReceiverPatternExpect[[]int]{ + value: [1, 2, 3] + }.dynamic_array_receiver_pattern_pairs_with_method_generic(9, 'extra') + assert value == 1 + assert extra == 'extra' + explicit_value, explicit_extra := ReceiverPatternExpect[[]int]{ + value: [1, 2, 3] + }.dynamic_array_receiver_pattern_pairs_with_method_generic[string](9, 'explicit') + assert explicit_value == 1 + assert explicit_extra == 'explicit' +} + +fn test_map_receiver_pattern_binds_key_and_value_types() { + assert ReceiverPatternExpect[map[string]int]{ + value: { + 'one': 1 + } + }.map_receiver_pattern_returns_existing_or_default('one', 0) == 1 + assert ReceiverPatternExpect[map[int]string]{ + value: { + 2: 'two' + } + }.map_receiver_pattern_returns_existing_or_default(3, 'fallback') == 'fallback' +} + +fn test_map_receiver_pattern_method_generic_can_be_explicit() { + value, extra := ReceiverPatternExpect[map[string]int]{ + value: { + 'one': 1 + } + }.map_receiver_pattern_pairs_with_method_generic[string]('missing', 7, 'extra') + assert value == 7 + assert extra == 'extra' +} + +fn test_alias_to_array_receiver_pattern_binds_element_type() { + assert IntListExpect{ + value: [1, 2, 3] + }.dynamic_array_receiver_pattern_returns_first_or_value(9) == 1 + value, extra := IntListExpect{ + value: [1, 2, 3] + }.dynamic_array_receiver_pattern_pairs_with_method_generic[string](9, 'alias') + assert value == 1 + assert extra == 'alias' + inferred_value, inferred_extra := IntListExpect{ + value: [1, 2, 3] + }.dynamic_array_receiver_pattern_pairs_with_method_generic(9, 'inferred') + assert inferred_value == 1 + assert inferred_extra == 'inferred' +} + +fn test_alias_to_map_receiver_pattern_binds_key_and_value_types() { + assert StringIntMapExpect{ + value: { + 'one': 1 + } + }.map_receiver_pattern_returns_existing_or_default('one', 0) == 1 + value, extra := StringIntMapExpect{ + value: { + 'one': 1 + } + }.map_receiver_pattern_pairs_with_method_generic[bool]('missing', 7, true) + assert value == 7 + assert extra +} + +fn test_alias_to_map_exact_generic_method_takes_precedence_over_structured_pattern() { + assert StringIntMapExpect{ + value: { + 'one': 1 + } + }.same_name_map_exact_prefers_concrete('alias') == 'concrete' +} + +fn test_alias_to_map_exact_method_takes_precedence_over_structured_pattern() { + assert StringIntMapExpect{ + value: { + 'one': 1 + } + }.same_name_map_method() == 'concrete' +} + +fn test_alias_receiver_exact_method_takes_precedence_over_structured_pattern() { + assert IntListExpect{ + value: [1, 2, 3] + }.same_name_prefers_concrete_receiver() == 'concrete' + assert IntListExpect{ + value: [1] + }.same_name_with_method_generic_prefers_concrete_receiver[string]('extra') == 'concrete' +} + +fn test_mixed_direct_and_structured_receiver_pattern_method_generic() { + direct, item, marker := MixedReceiverPatternBox[int, []string]{ + direct: 7 + items: ['alpha'] + }.mixed_receiver_pattern_values_with_method_generic[bool]('fallback', true) + assert direct == 7 + assert item == 'alpha' + assert marker + inferred_direct, inferred_item, inferred_marker := MixedReceiverPatternBox[int, []string]{ + direct: 9 + items: []string{} + }.mixed_receiver_pattern_values_with_method_generic('fallback', 1.5) + assert inferred_direct == 9 + assert inferred_item == 'fallback' + assert inferred_marker == 1.5 +} + +fn test_alias_to_mixed_receiver_pattern_binds_direct_and_structured_slots() { + direct, item, marker := MixedIntStringListBox{ + direct: 7 + items: ['alpha'] + }.mixed_receiver_pattern_values_with_method_generic[bool]('fallback', true) + assert direct == 7 + assert item == 'alpha' + assert marker + assert MixedIntStringListBox{ + direct: 1 + items: ['alpha'] + }.same_name_with_method_generic_prefers_concrete_mixed_receiver[bool](true) == 'concrete' +} + +fn test_exact_mixed_receiver_method_with_method_generic_takes_precedence_over_structured_pattern() { + assert MixedReceiverPatternBox[int, []string]{ + direct: 1 + items: ['alpha'] + }.same_name_with_method_generic_prefers_concrete_mixed_receiver[bool](true) == 'concrete' + assert MixedReceiverPatternBox[int, []bool]{ + direct: 1 + items: [true] + }.same_name_with_method_generic_prefers_concrete_mixed_receiver[string]('marker') == 'pattern' +} + +fn test_map_receiver_pattern_binds_nested_value_type() { + assert ReceiverPatternExpect[map[string]map[string]int]{ + value: { + 'outer': { + 'inner': 7 + } + } + }.map_receiver_pattern_returns_existing_or_default('outer', { + 'default': 0 + })['inner'] == 7 +} -- 2.39.5