mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-20 14:25:23 +00:00
handle explicit generic routine instantiations in sigmatch (#24010)
fixes #16376
The way the compiler handled generic proc instantiations in calls (like
`foo[int](...)`) up to this point was to instantiate `foo[int]`, create
a symbol for the instantiated proc (or a symchoice for multiple procs
excluding ones with mismatching generic param counts), then perform
overload resolution on this symbol/symchoice. The exception to this was
when the called symbol was already a symchoice node, in which case it
wasn't instantiated and overloading was called directly ([these
lines](b7b1313d21/compiler/semexprs.nim (L3366-L3371))).
This has several problems:
* Templates and macros can't create instantiated symbols, so they
couldn't participate in overloaded explicit generic instantiations,
causing the issue #16376.
* Every single proc that can be instantiated with the given generic
params is fully instantiated including the body. #9997 is about this but
isn't fixed here since the instantiation isn't in a call.
The way overload resolution handles explicit instantiations by itself is
also buggy:
* It doesn't check constraints.
* It allows only partially providing the generic parameters, which makes
sense for implicit generics, but can cause ambiguity in overloading.
Here is how this PR deals with these problems:
* Overload resolution now always handles explicit generic instantiations
in calls, in `initCandidate`, as long as the symbol resolves to a
routine symbol.
* Overload resolution now checks the generic params for constraints and
correct parameter count (ignoring implicit params). If these don't
match, the entire overload is considered as not matching and not
instantiated.
* Special error messages are added for mismatching/missing/extra generic
params. This is almost all of the diff in `semcall`.
* Procs with matching generic parameters now instantiate only the type
of the signature in overload resolution, not the proc itself, which also
works for templates and macros.
Unfortunately we can't entirely remove instantiations because overload
resolution can't handle some cases with uninstantiated types even though
it's resolved in the binding (see the last 2 blocks in
`texplicitgenerics`). There are also some instantiation issues with
default params that #24005 didn't fix but I didn't want this to become
the 3rd huge generics PR in a row so I didn't dive too deep into trying
to fix them. There is still a minor instantiation fix in `semtypinst`
though for subscripts in calls.
Additional changes:
* Overloading of `[]` wasn't documented properly, it somewhat is now
because we need to mention the limitation that it can't be done for
generic procs/types.
* Tests can now enable the new type mismatch errors with just
`-d:testsConciseTypeMismatch` in the command.
Package PRs:
- using fork for now:
[combparser](https://github.com/PMunch/combparser/pull/7) (partial
generic instantiation)
- merged: [cligen](https://github.com/c-blake/cligen/pull/233) (partial
generic instantiation but non-overloaded + template)
- merged: [neo](https://github.com/andreaferretti/neo/pull/56) (trying
to instantiate template with no generic param)
This commit is contained in:
@@ -16,7 +16,7 @@ template n[T, U](x: U): T =
|
||||
|
||||
proc k() =
|
||||
var res: A
|
||||
m(n[B](res))
|
||||
m(n[B, A](res))
|
||||
|
||||
proc w(mounter: U) = discard
|
||||
|
||||
@@ -24,4 +24,4 @@ proc mount(proto: U) = discard
|
||||
proc v() = mount k
|
||||
|
||||
# This is required for failure
|
||||
w(v)
|
||||
w(v)
|
||||
|
||||
@@ -43,6 +43,7 @@ switch("define", "nimPreviewRangeDefault")
|
||||
switch("define", "nimPreviewNonVarDestructor")
|
||||
|
||||
switch("warningAserror", "UnnamedBreak")
|
||||
switch("legacy", "verboseTypeMismatch")
|
||||
when not defined(testsConciseTypeMismatch):
|
||||
switch("legacy", "verboseTypeMismatch")
|
||||
switch("experimental", "vtables")
|
||||
switch("experimental", "openSym")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
discard """
|
||||
cmd: "nim c --hints:off --skipParentCfg $file"
|
||||
cmd: "nim c --hints:off -d:testsConciseTypeMismatch $file"
|
||||
errormsg: "type mismatch"
|
||||
nimout: '''
|
||||
tconcisetypemismatch.nim(23, 47) Error: type mismatch
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
switch("path", "$lib/../testament/lib")
|
||||
# so we can `import stdtest/foo` inside tests
|
||||
# Using $lib/../ instead of $nim/ so you can use a different nim to run tests
|
||||
# during local testing, e.g. nim --lib:lib.
|
||||
|
||||
## prevent common user config settings to interfere with testament expectations
|
||||
## Indifidual tests can override this if needed to test for these options.
|
||||
switch("colors", "off")
|
||||
|
||||
switch("excessiveStackTrace", "off")
|
||||
|
||||
when (NimMajor, NimMinor, NimPatch) >= (1,5,1):
|
||||
# to make it easier to test against older nim versions, (best effort only)
|
||||
switch("filenames", "legacyRelProj")
|
||||
switch("spellSuggest", "0")
|
||||
|
||||
# for std/unittest
|
||||
switch("define", "nimUnittestOutputLevel:PRINT_FAILURES")
|
||||
switch("define", "nimUnittestColor:off")
|
||||
|
||||
hint("Processing", off)
|
||||
26
tests/errmsgs/twrong_explicit_typeargs.nim
Normal file
26
tests/errmsgs/twrong_explicit_typeargs.nim
Normal file
@@ -0,0 +1,26 @@
|
||||
discard """
|
||||
cmd: "nim c --hints:off -d:testsConciseTypeMismatch $file"
|
||||
action: reject
|
||||
nimout: '''
|
||||
twrong_explicit_typeargs.nim(26, 29) Error: type mismatch
|
||||
Expression: newImage[string](320, 200)
|
||||
[1] 320: int literal(320)
|
||||
[2] 200: int literal(200)
|
||||
|
||||
Expected one of (first mismatch at [position]):
|
||||
[1] proc newImage[T: int32 | int64](w, h: int): ref Image[T]
|
||||
generic parameter mismatch, expected int32 or int64 but got 'string' of type: typedesc[string]
|
||||
'''
|
||||
"""
|
||||
|
||||
# bug #4084
|
||||
type
|
||||
Image[T] = object
|
||||
data: seq[T]
|
||||
|
||||
proc newImage[T: int32|int64](w, h: int): ref Image[T] =
|
||||
new(result)
|
||||
result.data = newSeq[T](w * h)
|
||||
|
||||
var correct = newImage[int32](320, 200)
|
||||
var wrong = newImage[string](320, 200)
|
||||
25
tests/errmsgs/twrong_explicit_typeargs_legacy.nim
Normal file
25
tests/errmsgs/twrong_explicit_typeargs_legacy.nim
Normal file
@@ -0,0 +1,25 @@
|
||||
discard """
|
||||
action: reject
|
||||
nimout: '''
|
||||
twrong_explicit_typeargs_legacy.nim(25, 29) Error: type mismatch: got <int literal(320), int literal(200)>
|
||||
but expected one of:
|
||||
proc newImage[T: int32 | int64](w, h: int): ref Image[T]
|
||||
first type mismatch at position: 1 in generic parameters
|
||||
required type for T: int32 or int64
|
||||
but expression 'string' is of type: typedesc[string]
|
||||
|
||||
expression: newImage[string](320, 200)
|
||||
'''
|
||||
"""
|
||||
|
||||
# bug #4084
|
||||
type
|
||||
Image[T] = object
|
||||
data: seq[T]
|
||||
|
||||
proc newImage[T: int32|int64](w, h: int): ref Image[T] =
|
||||
new(result)
|
||||
result.data = newSeq[T](w * h)
|
||||
|
||||
var correct = newImage[int32](320, 200)
|
||||
var wrong = newImage[string](320, 200)
|
||||
@@ -3,8 +3,8 @@ block: # basic test
|
||||
proc doStuff[T](a: SomeInteger): T = discard
|
||||
proc doStuff[T;Y](a: SomeInteger, b: Y): Y = discard
|
||||
assert typeof(doStuff[int](100)) is int
|
||||
assert typeof(doStuff[int](100, 1.0)) is float
|
||||
assert typeof(doStuff[int](100, "Hello")) is string
|
||||
assert typeof(doStuff[int, float](100, 1.0)) is float
|
||||
assert typeof(doStuff[int, string](100, "Hello")) is string
|
||||
|
||||
proc t[T](x: T; z: int | float): seq[T] = result.add(x & $z)
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
discard """
|
||||
errormsg: "cannot instantiate: 'newImage[string]'"
|
||||
line: 16
|
||||
"""
|
||||
|
||||
# bug #4084
|
||||
type
|
||||
Image[T] = object
|
||||
data: seq[T]
|
||||
|
||||
proc newImage[T: int32|int64](w, h: int): ref Image[T] =
|
||||
new(result)
|
||||
result.data = newSeq[T](w * h)
|
||||
|
||||
var correct = newImage[int32](320, 200)
|
||||
var wrong = newImage[string](320, 200)
|
||||
24
tests/proc/texplicitgenericcount.nim
Normal file
24
tests/proc/texplicitgenericcount.nim
Normal file
@@ -0,0 +1,24 @@
|
||||
discard """
|
||||
cmd: "nim check -d:testsConciseTypeMismatch $file"
|
||||
"""
|
||||
|
||||
proc foo[T, U](x: T, y: U): (T, U) = (x, y)
|
||||
|
||||
let x = foo[int](1, 2) #[tt.Error
|
||||
^ type mismatch
|
||||
Expression: foo[int](1, 2)
|
||||
[1] 1: int literal(1)
|
||||
[2] 2: int literal(2)
|
||||
|
||||
Expected one of (first mismatch at [position]):
|
||||
[2] proc foo[T, U](x: T; y: U): (T, U)
|
||||
missing generic parameter: U]#
|
||||
let y = foo[int, float, string](1, 2) #[tt.Error
|
||||
^ type mismatch
|
||||
Expression: foo[int, float, string](1, 2)
|
||||
[1] 1: int literal(1)
|
||||
[2] 2: int literal(2)
|
||||
|
||||
Expected one of (first mismatch at [position]):
|
||||
[3] proc foo[T, U](x: T; y: U): (T, U)
|
||||
extra generic param given]#
|
||||
22
tests/proc/texplicitgenericcountverbose.nim
Normal file
22
tests/proc/texplicitgenericcountverbose.nim
Normal file
@@ -0,0 +1,22 @@
|
||||
discard """
|
||||
cmd: "nim check $file"
|
||||
"""
|
||||
|
||||
proc foo[T, U](x: T, y: U): (T, U) = (x, y)
|
||||
|
||||
let x = foo[int](1, 2) #[tt.Error
|
||||
^ type mismatch: got <int literal(1), int literal(2)>
|
||||
but expected one of:
|
||||
proc foo[T, U](x: T; y: U): (T, U)
|
||||
first type mismatch at position: 2 in generic parameters
|
||||
missing generic parameter: U
|
||||
|
||||
expression: foo[int](1, 2)]#
|
||||
let y = foo[int, float, string](1, 2) #[tt.Error
|
||||
^ type mismatch: got <int literal(1), int literal(2)>
|
||||
but expected one of:
|
||||
proc foo[T, U](x: T; y: U): (T, U)
|
||||
first type mismatch at position: 3 in generic parameters
|
||||
extra generic param given
|
||||
|
||||
expression: foo[int, float, string](1, 2)]#
|
||||
49
tests/proc/texplicitgenerics.nim
Normal file
49
tests/proc/texplicitgenerics.nim
Normal file
@@ -0,0 +1,49 @@
|
||||
block: # issue #16376
|
||||
type
|
||||
Matrix[T] = object
|
||||
data: T
|
||||
proc randMatrix[T](m, n: int, max: T): Matrix[T] = discard
|
||||
proc randMatrix[T](m, n: int, x: Slice[T]): Matrix[T] = discard
|
||||
template randMatrix[T](m, n: int): Matrix[T] = randMatrix[T](m, n, T(1.0))
|
||||
let B = randMatrix[float32](20, 10)
|
||||
|
||||
block: # different generic param counts
|
||||
type
|
||||
Matrix[T] = object
|
||||
data: T
|
||||
proc randMatrix[T](m: T, n: T): Matrix[T] = Matrix[T](data: T(1.0))
|
||||
proc randMatrix[T; U: not T](m: T, n: U): (Matrix[T], U) = (Matrix[T](data: T(1.0)), default(U))
|
||||
let b = randMatrix[float32](20, 10)
|
||||
doAssert b == Matrix[float32](data: 1.0)
|
||||
|
||||
block: # above for templates
|
||||
type
|
||||
Matrix[T] = object
|
||||
data: T
|
||||
template randMatrix[T](m: T, n: T): Matrix[T] = Matrix[T](data: T(1.0))
|
||||
template randMatrix[T; U: not T](m: T, n: U): (Matrix[T], U) = (Matrix[T](data: T(1.0)), default(U))
|
||||
let b = randMatrix[float32](20, 10)
|
||||
doAssert b == Matrix[float32](data: 1.0)
|
||||
|
||||
block: # sigmatch can't handle this without pre-instantiating the type:
|
||||
# minimized from numericalnim
|
||||
type Foo[T] = proc (x: T)
|
||||
proc foo[T](x: T) = discard
|
||||
proc bar[T](f: Foo[T]) = discard
|
||||
bar[int](foo)
|
||||
|
||||
block: # ditto but may be wrong minimization
|
||||
# minimized from measuremancer
|
||||
type Foo[T] = object
|
||||
proc foo[T](): Foo[T] = Foo[T]()
|
||||
when false:
|
||||
# this is the actual issue but there are other instantiation problems
|
||||
proc bar[T](x = foo[T]()) = discard
|
||||
else:
|
||||
proc bar[T](x: Foo[T] = foo[T]()) = discard
|
||||
bar[int](Foo[int]())
|
||||
bar[int]()
|
||||
when false:
|
||||
# alternative version, also causes instantiation issue
|
||||
proc baz[T](x: typeof(foo[T]())) = discard
|
||||
baz[int](Foo[int]())
|
||||
Reference in New Issue
Block a user