-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fixes #4086; allow generic type parameter defaults to reference earlier type params #25799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: devel
Are you sure you want to change the base?
Changes from 6 commits
18f250e
305c731
649f346
1f0c6cd
a201fea
5faad66
f2551c5
56c1984
267264a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3115,6 +3115,29 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) = | |
| if nfDefaultRefsParam in formal.ast.flags: | ||
| m.call.flags.incl nfDefaultRefsParam | ||
| var defaultValue = copyTree(formal.ast) | ||
| # Issues #4086, #9355: when the default value is a bare reference | ||
| # to an earlier generic type param (e.g. `func foo[T](U: type = T)` | ||
| # or its untyped form `func foo[T](U = T)`) — recognised by the | ||
| # default's static type being `tyGenericParam` — substitute T | ||
| # against the explicit-instantiation bindings via | ||
| # `prepareTypesInBody` (which updates both the AST sym and its | ||
| # typ) and wrap the result as `tyTypeDesc` so the | ||
| # `tfImplicitTypeParam` binding path below treats it like a | ||
| # literal type default (`U: type = int`). | ||
| # Other defaults (value expressions like `arr.high`, proc-call | ||
| # expressions like `newTensor[T](0)` whose result type is a | ||
| # `tyGenericInvocation`/`tyGenericInst`, etc.) have a non- | ||
| # `tyGenericParam` type and are left untouched, so the existing | ||
| # default-handling machinery takes care of them. | ||
| if defaultValue.typ != nil and | ||
| defaultValue.typ.kind == tyGenericParam and | ||
| m.calleeSym != nil: | ||
| defaultValue = prepareTypesInBody(c, m.bindings, defaultValue, m.calleeSym) | ||
| if formal.typ.kind == tyTypeDesc and defaultValue.typ != nil and | ||
| defaultValue.typ.kind != tyTypeDesc: | ||
| let typedescTyp = newTypeS(tyTypeDesc, c, defaultValue.typ) | ||
| typedescTyp.incl tfCheckedForDestructor | ||
| defaultValue.typ = typedescTyp | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Look, this is |
||
| if defaultValue.kind == nkNilLit: | ||
| defaultValue = implicitConv(nkHiddenStdConv, formal.typ, defaultValue, m, c) | ||
| # proc foo(x: T = 0.0) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| discard """ | ||
| matrix: "; --mm:refc" | ||
| """ | ||
|
|
||
| import std/deques | ||
|
|
||
| # Type-side generic param defaults that reference other type parameters | ||
| # (issue #4086). | ||
| # | ||
| # Currently fails on Nim 2.3.1 devel: | ||
| # `Error: invalid type: 'Foo[system.int, seq[T]]' for var` | ||
| # | ||
| # Other languages (C++, TypeScript, Rust, Scala) all support this pattern. | ||
| # Nim already supports T-independent defaults like `[T; U = int]`; this | ||
| # extends support to type-side defaults that reference earlier type params | ||
| # (e.g. `type Foo[T; U = seq[T]]`). This is needed for binding C++ templates | ||
| # like `std::vector<T, allocator<T>>` and `std::unique_ptr<T, default_delete<T>>`. | ||
| # | ||
| # When every generic param has a default, `Foo[]` (empty brackets) instantiates | ||
| # `Foo` using all defaults, e.g. `type Foo[T = int]; var f: Foo[]` is `Foo[int]`. | ||
| # `Foo[]` is the explicit-instantiation spelling (mirrors C++ `Foo<>`); a bare | ||
| # `Foo` keeps meaning a type class in parameter position, so it is intentionally | ||
| # not auto-instantiated. `Foo[]` works uniformly in every type position. | ||
| # | ||
| # Proc-side: the typedesc-parameter form `func foo[T](U: type = T)` is also | ||
| # supported (issue #9355) -- the default `= T` is substituted during overload | ||
| # matching (sigmatch). The untyped form `func foo[T](U = T)` is rejected by | ||
| # design (a type is not a value), and brackets-internal proc generic defaults | ||
| # (`proc foo[T, U = T]()`) are out of scope for this PR. | ||
|
|
||
| block: # #4086 type-side: object generic param default `seq[T]` | ||
| type Foo[T; U = seq[T]] = object | ||
| data: U | ||
| var f: Foo[int] | ||
| f.data.add 42 | ||
| doAssert f.data == @[42] | ||
|
|
||
| block: # nested reference: U default uses T, V default uses U | ||
| type Foo[T; U = seq[T]; V = seq[U]] = object | ||
| data: V | ||
| var f: Foo[int] | ||
| f.data.add @[1, 2] | ||
| doAssert f.data == @[@[1, 2]] | ||
|
|
||
| block: # type-side direct: U = T | ||
| type Foo[T; U = T] = object | ||
| a: T | ||
| b: U | ||
| var f: Foo[int] | ||
| f.a = 1 | ||
| f.b = 2 | ||
| doAssert f.b is int | ||
|
|
||
| block: # type-side compound: U = ref T | ||
| type Foo[T; U = ref T] = object | ||
| p: U | ||
| var f: Foo[int] | ||
| f.p = new(int) | ||
| f.p[] = 9 | ||
| doAssert f.p[] == 9 | ||
|
|
||
| block: # type-side compound: U = array[3, T] | ||
| type Foo[T; U = array[3, T]] = object | ||
| arr: U | ||
| var f: Foo[int] | ||
| f.arr[0] = 10 | ||
| f.arr[2] = 30 | ||
| doAssert f.arr[0] == 10 | ||
| doAssert f.arr[2] == 30 | ||
|
|
||
| block: # #4086 original: all params defaulted, `Foo[]` uses all defaults | ||
| type Foo[T = int] = object | ||
| x: T | ||
| var f: Foo[] | ||
| f.x = 42 | ||
| doAssert f.x == 42 | ||
| doAssert f.x is int | ||
|
|
||
| block: # alias of a defaulted instantiation | ||
| type Foo[T; U = T] = object | ||
| a: T | ||
| b: U | ||
| type IntFoo = Foo[int] | ||
| var f: IntFoo | ||
| f.a = 1 | ||
| f.b = 2 | ||
| doAssert f.b is int | ||
|
|
||
| block: # distinct of a defaulted instantiation | ||
| type Foo[T; U = seq[T]] = object | ||
| data: U | ||
| type DistFoo = distinct Foo[int] | ||
| var f: DistFoo | ||
| Foo[int](f).data.add 7 | ||
| doAssert Foo[int](f).data == @[7] | ||
|
|
||
| block: # union constraint + default referencing T (review request) | ||
| type Foo[T; U: seq[T]|Deque[T] = seq[T]] = object | ||
| data: U | ||
| var f: Foo[int] | ||
| f.data.add 42 | ||
| doAssert f.data is seq[int] | ||
| doAssert f.data == @[42] | ||
|
|
||
| block: # typeclass constraint + concrete-type default (review request) | ||
| type Foo[T: SomeInteger = int] = object | ||
| x: T | ||
| var f: Foo[] | ||
| f.x = 7 | ||
| doAssert f.x is int | ||
| var g: Foo[int64] | ||
| g.x = 9'i64 | ||
| doAssert g.x is int64 | ||
|
|
||
| block: # concept constraint + default (review request) | ||
| type HasLen = concept x | ||
| x.len is int | ||
| type Foo[T: HasLen = string] = object | ||
| val: T | ||
| var f: Foo[] | ||
| f.val = "hi" | ||
| doAssert f.val.len == 2 | ||
| var g: Foo[seq[int]] | ||
| g.val = @[1, 2, 3] | ||
| doAssert g.val.len == 3 | ||
|
|
||
| block: # `Foo[]` with all params defaulted AND a dependent default (cascade) | ||
| type Foo[T = int; U = seq[T]; V = seq[U]] = object | ||
| a: T | ||
| b: U | ||
| c: V | ||
| var f: Foo[] | ||
| f.a = 1 | ||
| f.b.add 2 | ||
| f.c.add @[3, 4] | ||
| doAssert f.a is int | ||
| doAssert f.b == @[2] | ||
| doAssert f.c == @[@[3, 4]] | ||
|
|
||
| block: # `Foo[]` works uniformly in every type position (the point of the syntax) | ||
| type Foo[T = int] = object | ||
| x: T | ||
| # proc param position -> concrete Foo[int], NOT a generic proc | ||
| proc takesFoo(g: Foo[]): int = g.x | ||
| # return type position | ||
| proc makeFoo(): Foo[] = | ||
| result.x = 9 | ||
| # field position | ||
| type Holder = object | ||
| inner: Foo[] | ||
| var h: Holder | ||
| h.inner.x = 5 | ||
| doAssert takesFoo(h.inner) == 5 | ||
| doAssert makeFoo().x == 9 | ||
|
|
||
| block: # #9355: typedesc parameter default referencing an earlier generic param | ||
| func foo[T](U: type = T): U = default(U) | ||
| doAssert foo[int]() is int # U defaults to T | ||
| doAssert foo[string]() is string | ||
| doAssert foo[int](float) is float # explicit override still works |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But why is this prepass even necessary... You instantiate the type, when the typevar wasn't bound, you see if it had a default and use that, else it's an error. That's the algorithm and Nim seems to never do things properly and instead special cases everything and thus sem.nim grows like a cancer.