Skip to content

Commit a8cc0bb

Browse files
authored
all: structured generic receiver patterns (#27079)
1 parent eaad379 commit a8cc0bb

17 files changed

Lines changed: 1534 additions & 527 deletions

doc/docs.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4716,6 +4716,76 @@ println(compare(1.1, 1.1)) // 0
47164716
println(compare(1.1, 1.2)) // -1
47174717
```
47184718

4719+
#### Structured generic receiver patterns
4720+
4721+
Generic methods can constrain their receiver to a *structured* shape of the
4722+
wrapped type, and the checker will bind the inner type parameters from the
4723+
concrete receiver.
4724+
4725+
The most common case is requiring the wrapped type to be a dynamic array:
4726+
4727+
```v
4728+
struct Expect[T] {
4729+
value T
4730+
}
4731+
4732+
fn (e Expect[[]T]) first_or(value T) T {
4733+
if e.value.len == 0 {
4734+
return value
4735+
}
4736+
return e.value[0]
4737+
}
4738+
4739+
fn main() {
4740+
a := Expect[[]int]{value: [1, 2, 3]}
4741+
println(a.first_or(0)) // 1, T is bound to int
4742+
4743+
b := Expect[[]string]{value: []string{}}
4744+
println(b.first_or('none')) // none, T is bound to string
4745+
}
4746+
```
4747+
4748+
Nested array patterns are matched recursively, so `Expect[[][]int]` binds
4749+
`T = []int`, and `Expect[[]map[string]int]` binds `T = map[string]int`.
4750+
4751+
Maps work the same way and can bind two parameters at once:
4752+
4753+
```v
4754+
struct Expect[T] {
4755+
value T
4756+
}
4757+
4758+
fn (e Expect[map[K]V]) get_or(key K, value V) V {
4759+
if key in e.value {
4760+
return e.value[key]
4761+
}
4762+
return value
4763+
}
4764+
4765+
fn main() {
4766+
m := Expect[map[string]int]{value: {'a': 1}}
4767+
println(m.get_or('a', 0)) // 1 (K = string, V = int)
4768+
println(m.get_or('b', -1)) // -1
4769+
}
4770+
```
4771+
4772+
`Expect[map[string]map[string]int]` binds `K = string` and
4773+
`V = map[string]int`.
4774+
4775+
Rules:
4776+
4777+
- `Expect[[]T]` matches dynamic arrays; fixed-size arrays are not matched.
4778+
- `Expect[map[K]V]` matches maps.
4779+
- Nested patterns are matched recursively.
4780+
- Plain generic receivers like `Expect[T]` continue to work, and an exact
4781+
concrete receiver method (for example `fn (e Expect[[]int]) ...`) keeps
4782+
precedence over a structured pattern.
4783+
- A repeated placeholder must bind consistently: `Expect[map[K]K]` on
4784+
`Expect[map[string]int]` is rejected because `K` cannot be both `string`
4785+
and `int`.
4786+
- Raw `voidptr` is not allowed to bind into these patterns; cast through an
4787+
explicit V type instead.
4788+
47194789
## Concurrency
47204790

47214791
### Spawning Concurrent Tasks

vlib/v/ast/table.v

Lines changed: 187 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -65,31 +65,32 @@ pub struct Table {
6565
mut:
6666
parsing_type string // name of the type to enable recursive type parsing
6767
pub mut:
68-
type_symbols []&TypeSymbol
69-
type_idxs map[string]int
70-
fns map[string]Fn
71-
iface_types map[string][]Type
72-
dumps map[int]string // needed for efficiently generating all _v_dump_expr_TNAME() functions
73-
imports []string // List of all imports
74-
modules []string // Topologically sorted list of all modules registered by the application
75-
global_scope &Scope = unsafe { nil }
76-
cflags []cflag.CFlag
77-
redefined_fns []string
78-
fn_generic_types map[string][][]Type // for generic functions
79-
interfaces map[int]InterfaceDecl
80-
sumtypes map[int]SumTypeDecl
81-
cmod_prefix string // needed for ast.type_to_str(Type) while vfmt; contains `os.`
82-
is_fmt bool
83-
used_features &UsedFeatures = &UsedFeatures{} // filled in by the builder via markused module, when pref.skip_unused = true;
84-
veb_res_idx_cache int // Cache of `veb.Result` type
85-
veb_ctx_idx_cache int // Cache of `veb.Context` type
86-
panic_handler FnPanicHandler = default_table_panic_handler
87-
panic_userdata voidptr = unsafe { nil } // can be used to pass arbitrary data to panic_handler;
88-
panic_npanics int
89-
cur_fn &FnDecl = unsafe { nil } // previously stored in Checker.cur_fn and Gen.cur_fn
90-
cur_lambda &LambdaExpr = unsafe { nil } // current lambda node
91-
cur_concrete_types []Type // current concrete types, e.g. [int, string]
92-
gostmts int // how many `go` statements there were in the parsed files.
68+
type_symbols []&TypeSymbol
69+
type_idxs map[string]int
70+
fns map[string]Fn
71+
iface_types map[string][]Type
72+
dumps map[int]string // needed for efficiently generating all _v_dump_expr_TNAME() functions
73+
imports []string // List of all imports
74+
modules []string // Topologically sorted list of all modules registered by the application
75+
global_scope &Scope = unsafe { nil }
76+
cflags []cflag.CFlag
77+
redefined_fns []string
78+
fn_generic_types map[string][][]Type // for generic functions
79+
structured_receiver_methods map[string][]Fn
80+
interfaces map[int]InterfaceDecl
81+
sumtypes map[int]SumTypeDecl
82+
cmod_prefix string // needed for ast.type_to_str(Type) while vfmt; contains `os.`
83+
is_fmt bool
84+
used_features &UsedFeatures = &UsedFeatures{} // filled in by the builder via markused module, when pref.skip_unused = true;
85+
veb_res_idx_cache int // Cache of `veb.Result` type
86+
veb_ctx_idx_cache int // Cache of `veb.Context` type
87+
panic_handler FnPanicHandler = default_table_panic_handler
88+
panic_userdata voidptr = unsafe { nil } // can be used to pass arbitrary data to panic_handler;
89+
panic_npanics int
90+
cur_fn &FnDecl = unsafe { nil } // previously stored in Checker.cur_fn and Gen.cur_fn
91+
cur_lambda &LambdaExpr = unsafe { nil } // current lambda node
92+
cur_concrete_types []Type // current concrete types, e.g. [int, string]
93+
gostmts int // how many `go` statements there were in the parsed files.
9394
// When table.gostmts > 0, __VTHREADS__ is defined, which can be checked with `$if threads {`
9495
enum_decls map[string]EnumDecl
9596
vls_info map[string]VlsInfo
@@ -390,6 +391,21 @@ pub fn (mut t TypeSymbol) register_method(new_fn Fn) int {
390391
return t.methods.len - 1
391392
}
392393

394+
fn receiver_pattern_method_key(parent_idx int, name string) string {
395+
return '${parent_idx}.${name}'
396+
}
397+
398+
pub fn (mut t Table) register_structured_receiver_method(new_fn Fn) {
399+
if !t.receiver_type_is_structured_generic_pattern(new_fn.receiver_type) {
400+
return
401+
}
402+
receiver := t.receiver_generic_types(new_fn.receiver_type) or { return }
403+
key := receiver_pattern_method_key(receiver.parent_idx, new_fn.name)
404+
mut methods := t.structured_receiver_methods[key] or { []Fn{} }
405+
methods << new_fn
406+
t.structured_receiver_methods[key] = methods
407+
}
408+
393409
pub fn (mut t TypeSymbol) update_method(f Fn) int {
394410
for i, m in t.methods {
395411
if m.name == f.name {
@@ -4085,6 +4101,14 @@ fn (mut t Table) should_auto_register_concrete_method(method Fn, parent_type Typ
40854101
if parent_idx == 0 || method.generic_names.len != concrete_types.len {
40864102
return false
40874103
}
4104+
for concrete_type in concrete_types {
4105+
if t.concrete_type_has_unresolved_generic_parts(concrete_type) {
4106+
return false
4107+
}
4108+
}
4109+
if t.receiver_type_is_structured_generic_pattern(method.receiver_type) {
4110+
return false
4111+
}
40884112
for i in 1 .. method.params.len {
40894113
param := method.params[i]
40904114
mut param_typ := param.typ
@@ -4112,6 +4136,144 @@ fn (mut t Table) should_auto_register_concrete_method(method Fn, parent_type Typ
41124136
return !t.type_contains_transformed_parent_inst(return_type, parent_idx, concrete_types)
41134137
}
41144138

4139+
fn (t &Table) concrete_type_has_unresolved_generic_parts(typ Type) bool {
4140+
if typ == 0 {
4141+
return false
4142+
}
4143+
if typ.has_flag(.generic) {
4144+
return true
4145+
}
4146+
idx := typ.idx()
4147+
if idx <= nil_type_idx {
4148+
return false
4149+
}
4150+
sym := t.sym(typ)
4151+
if sym.kind == .placeholder || (sym.kind == .any && !sym.is_builtin()) {
4152+
return true
4153+
}
4154+
match sym.info {
4155+
Array {
4156+
return t.concrete_type_has_unresolved_generic_parts(sym.info.elem_type)
4157+
}
4158+
ArrayFixed {
4159+
return t.concrete_type_has_unresolved_generic_parts(sym.info.elem_type)
4160+
}
4161+
Chan {
4162+
return t.concrete_type_has_unresolved_generic_parts(sym.info.elem_type)
4163+
}
4164+
Thread {
4165+
return t.concrete_type_has_unresolved_generic_parts(sym.info.return_type)
4166+
}
4167+
Map {
4168+
if t.concrete_type_has_unresolved_generic_parts(sym.info.key_type) {
4169+
return true
4170+
}
4171+
return t.concrete_type_has_unresolved_generic_parts(sym.info.value_type)
4172+
}
4173+
FnType {
4174+
if t.concrete_type_has_unresolved_generic_parts(sym.info.func.return_type) {
4175+
return true
4176+
}
4177+
for param in sym.info.func.params {
4178+
if t.concrete_type_has_unresolved_generic_parts(param.typ) {
4179+
return true
4180+
}
4181+
}
4182+
return false
4183+
}
4184+
MultiReturn {
4185+
for typ_ in sym.info.types {
4186+
if t.concrete_type_has_unresolved_generic_parts(typ_) {
4187+
return true
4188+
}
4189+
}
4190+
return false
4191+
}
4192+
Struct {
4193+
if sym.info.concrete_types.len > 0 {
4194+
for concrete_type in sym.info.concrete_types {
4195+
if t.concrete_type_has_unresolved_generic_parts(concrete_type) {
4196+
return true
4197+
}
4198+
}
4199+
return false
4200+
}
4201+
if sym.generic_types.len == sym.info.generic_types.len
4202+
&& sym.generic_types != sym.info.generic_types {
4203+
for generic_type in sym.generic_types {
4204+
if t.concrete_type_has_unresolved_generic_parts(generic_type) {
4205+
return true
4206+
}
4207+
}
4208+
return false
4209+
}
4210+
return sym.info.generic_types.len > 0
4211+
}
4212+
Interface {
4213+
if sym.info.concrete_types.len > 0 {
4214+
for concrete_type in sym.info.concrete_types {
4215+
if t.concrete_type_has_unresolved_generic_parts(concrete_type) {
4216+
return true
4217+
}
4218+
}
4219+
return false
4220+
}
4221+
if sym.generic_types.len == sym.info.generic_types.len
4222+
&& sym.generic_types != sym.info.generic_types {
4223+
for generic_type in sym.generic_types {
4224+
if t.concrete_type_has_unresolved_generic_parts(generic_type) {
4225+
return true
4226+
}
4227+
}
4228+
return false
4229+
}
4230+
return sym.info.is_generic
4231+
}
4232+
SumType {
4233+
if sym.info.concrete_types.len > 0 {
4234+
for concrete_type in sym.info.concrete_types {
4235+
if t.concrete_type_has_unresolved_generic_parts(concrete_type) {
4236+
return true
4237+
}
4238+
}
4239+
return false
4240+
}
4241+
if sym.generic_types.len == sym.info.generic_types.len
4242+
&& sym.generic_types != sym.info.generic_types {
4243+
for generic_type in sym.generic_types {
4244+
if t.concrete_type_has_unresolved_generic_parts(generic_type) {
4245+
return true
4246+
}
4247+
}
4248+
return false
4249+
}
4250+
return sym.info.is_generic
4251+
}
4252+
GenericInst {
4253+
for concrete_type in sym.info.concrete_types {
4254+
if t.concrete_type_has_unresolved_generic_parts(concrete_type) {
4255+
return true
4256+
}
4257+
}
4258+
return false
4259+
}
4260+
UnknownTypeInfo {
4261+
if sym.name.contains('[') && sym.name.contains(']') {
4262+
args := sym.name.all_after('[').trim_right(']').split(',')
4263+
for arg in args {
4264+
if util.is_generic_type_name(arg.trim_space()) {
4265+
return true
4266+
}
4267+
}
4268+
}
4269+
return false
4270+
}
4271+
else {
4272+
return false
4273+
}
4274+
}
4275+
}
4276+
41154277
fn (mut t Table) unwrap_method_types(ts &TypeSymbol, generic_names []string, concrete_types []Type) {
41164278
mut needs_unwrap_types := []Type{}
41174279
for method in ts.get_methods() {

0 commit comments

Comments
 (0)