mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-07 05:23:20 +00:00
fix calls in generic bodies, delay typecheck when no overloads match (#22029)
* sacrifice "tgenericshardcases" for working statics * legacy switch for CI, maybe experimental later * convert to experimental * apparently untyped needs the experimental switch * try special case call semcheck * try fix * fix compilation * final cleanup, not experimental, make `when` work * remove last needed use of untyped * fix unused warning in test * remove untyped feature
This commit is contained in:
@@ -627,7 +627,10 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode,
|
||||
candidates)
|
||||
result = semResolvedCall(c, r, n, flags)
|
||||
else:
|
||||
if efExplain notin flags:
|
||||
if efDetermineType in flags and c.inGenericContext > 0 and c.matchedConcept == nil:
|
||||
result = n
|
||||
result.typ = makeTypeFromExpr(c, result.copyTree)
|
||||
elif efExplain notin flags:
|
||||
# repeat the overload resolution,
|
||||
# this time enabling all the diagnostic output (this should fail again)
|
||||
result = semOverloadedCall(c, n, nOrig, filter, flags + {efExplain})
|
||||
|
||||
@@ -971,7 +971,8 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
|
||||
|
||||
if result != nil:
|
||||
if result[0].kind != nkSym:
|
||||
internalError(c.config, "semOverloadedCallAnalyseEffects")
|
||||
if not (efDetermineType in flags and c.inGenericContext > 0):
|
||||
internalError(c.config, "semOverloadedCallAnalyseEffects")
|
||||
return
|
||||
let callee = result[0].sym
|
||||
case callee.kind
|
||||
@@ -1007,6 +1008,8 @@ proc setGenericParams(c: PContext, n: PNode) =
|
||||
proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags; expectedType: PType = nil): PNode =
|
||||
if efNoSemCheck notin flags and n.typ != nil and n.typ.kind == tyError:
|
||||
return errorNode(c, n)
|
||||
if n.typ != nil and n.typ.kind == tyFromExpr and c.inGenericContext > 0:
|
||||
return n
|
||||
|
||||
result = n
|
||||
|
||||
|
||||
@@ -388,8 +388,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
|
||||
pragma(c, result, n[pragmasPos], allRoutinePragmas)
|
||||
if isNil(n[bodyPos]):
|
||||
n[bodyPos] = copyTree(getBody(c.graph, fn))
|
||||
if c.inGenericContext == 0:
|
||||
instantiateBody(c, n, fn.typ.n, result, fn)
|
||||
instantiateBody(c, n, fn.typ.n, result, fn)
|
||||
sideEffectsCheck(c, result)
|
||||
if result.magic notin {mSlice, mTypeOf}:
|
||||
# 'toOpenArray' is special and it is allowed to return 'openArray':
|
||||
|
||||
@@ -269,6 +269,8 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
|
||||
localError(c.config, n.info, "range is empty")
|
||||
|
||||
var range: array[2, PNode]
|
||||
# XXX this is still a hard compilation in a generic context, this can
|
||||
# result in unresolved generic parameters being treated like real types
|
||||
range[0] = semExprWithType(c, n[1], {efDetermineType})
|
||||
range[1] = semExprWithType(c, n[2], {efDetermineType})
|
||||
|
||||
@@ -277,7 +279,7 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
|
||||
rangeT[i] = range[i].typ.skipTypes({tyStatic}).skipIntLit(c.idgen)
|
||||
|
||||
let hasUnknownTypes = c.inGenericContext > 0 and
|
||||
rangeT[0].kind == tyFromExpr or rangeT[1].kind == tyFromExpr
|
||||
(rangeT[0].kind == tyFromExpr or rangeT[1].kind == tyFromExpr)
|
||||
|
||||
if not hasUnknownTypes:
|
||||
if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})):
|
||||
@@ -337,6 +339,8 @@ proc semArrayIndex(c: PContext, n: PNode): PType =
|
||||
elif n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s == "..<":
|
||||
result = errorType(c)
|
||||
else:
|
||||
# XXX this is still a hard compilation in a generic context, this can
|
||||
# result in unresolved generic parameters being treated like real types
|
||||
let e = semExprWithType(c, n, {efDetermineType})
|
||||
if e.typ.kind == tyFromExpr:
|
||||
result = makeRangeWithStaticExpr(c, e.typ.n)
|
||||
@@ -357,7 +361,7 @@ proc semArrayIndex(c: PContext, n: PNode): PType =
|
||||
if not isOrdinalType(e.typ.skipTypes({tyStatic, tyAlias, tyGenericInst, tySink})):
|
||||
localError(c.config, n[1].info, errOrdinalTypeExpected % typeToString(e.typ, preferDesc))
|
||||
# This is an int returning call, depending on an
|
||||
# yet unknown generic param (see tgenericshardcases).
|
||||
# yet unknown generic param (see tuninstantiatedgenericcalls).
|
||||
# We are going to construct a range type that will be
|
||||
# properly filled-out in semtypinst (see how tyStaticExpr
|
||||
# is handled there).
|
||||
@@ -380,7 +384,8 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType =
|
||||
let indx = semArrayIndex(c, n[1])
|
||||
var indxB = indx
|
||||
if indxB.kind in {tyGenericInst, tyAlias, tySink}: indxB = lastSon(indxB)
|
||||
if indxB.kind notin {tyGenericParam, tyStatic, tyFromExpr}:
|
||||
if indxB.kind notin {tyGenericParam, tyStatic, tyFromExpr} and
|
||||
tfUnresolved notin indxB.flags:
|
||||
if indxB.skipTypes({tyRange}).kind in {tyUInt, tyUInt64}:
|
||||
discard
|
||||
elif not isOrdinalType(indxB):
|
||||
@@ -737,7 +742,13 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
|
||||
if e.kind != nkIntLit: discard "don't report followup error"
|
||||
elif e.intVal != 0 and branch == nil: branch = it[1]
|
||||
else:
|
||||
it[0] = forceBool(c, semExprWithType(c, it[0]))
|
||||
# XXX this is still a hard compilation in a generic context, this can
|
||||
# result in unresolved generic parameters being treated like real types
|
||||
let e = semExprWithType(c, it[0], {efDetermineType})
|
||||
if e.typ.kind == tyFromExpr:
|
||||
it[0] = makeStaticExpr(c, e)
|
||||
else:
|
||||
it[0] = forceBool(c, e)
|
||||
of nkElse:
|
||||
checkSonsLen(it, 1, c.config)
|
||||
if branch == nil: branch = it[0]
|
||||
|
||||
@@ -141,27 +141,28 @@ proc isTypeParam(n: PNode): bool =
|
||||
(n.sym.kind == skGenericParam or
|
||||
(n.sym.kind == skType and sfFromGeneric in n.sym.flags))
|
||||
|
||||
proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
|
||||
# This is needed for tgenericshardcases
|
||||
# It's possible that a generic param will be used in a proc call to a
|
||||
# typedesc accepting proc. After generic param substitution, such procs
|
||||
# should be optionally instantiated with the correct type. In order to
|
||||
# perform this instantiation, we need to re-run the generateInstance path
|
||||
# in the compiler, but it's quite complicated to do so at the moment so we
|
||||
# resort to a mild hack; the head symbol of the call is temporary reset and
|
||||
# overload resolution is executed again (which may trigger generateInstance).
|
||||
if n.kind in nkCallKinds and sfFromGeneric in n[0].sym.flags:
|
||||
var needsFixing = false
|
||||
for i in 1..<n.safeLen:
|
||||
if isTypeParam(n[i]): needsFixing = true
|
||||
if needsFixing:
|
||||
n[0] = newSymNode(n[0].sym.owner)
|
||||
return cl.c.semOverloadedCall(cl.c, n, n, {skProc, skFunc}, {})
|
||||
when false: # old workaround
|
||||
proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
|
||||
# This is needed for tuninstantiatedgenericcalls
|
||||
# It's possible that a generic param will be used in a proc call to a
|
||||
# typedesc accepting proc. After generic param substitution, such procs
|
||||
# should be optionally instantiated with the correct type. In order to
|
||||
# perform this instantiation, we need to re-run the generateInstance path
|
||||
# in the compiler, but it's quite complicated to do so at the moment so we
|
||||
# resort to a mild hack; the head symbol of the call is temporary reset and
|
||||
# overload resolution is executed again (which may trigger generateInstance).
|
||||
if n.kind in nkCallKinds and sfFromGeneric in n[0].sym.flags:
|
||||
var needsFixing = false
|
||||
for i in 1..<n.safeLen:
|
||||
if isTypeParam(n[i]): needsFixing = true
|
||||
if needsFixing:
|
||||
n[0] = newSymNode(n[0].sym.owner)
|
||||
return cl.c.semOverloadedCall(cl.c, n, n, {skProc, skFunc}, {})
|
||||
|
||||
for i in 0..<n.safeLen:
|
||||
n[i] = reResolveCallsWithTypedescParams(cl, n[i])
|
||||
for i in 0..<n.safeLen:
|
||||
n[i] = reResolveCallsWithTypedescParams(cl, n[i])
|
||||
|
||||
return n
|
||||
return n
|
||||
|
||||
proc replaceObjBranches(cl: TReplTypeVars, n: PNode): PNode =
|
||||
result = n
|
||||
@@ -250,7 +251,8 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0): PNode =
|
||||
result = newNodeI(nkRecList, n.info)
|
||||
of nkStaticExpr:
|
||||
var n = prepareNode(cl, n)
|
||||
n = reResolveCallsWithTypedescParams(cl, n)
|
||||
when false:
|
||||
n = reResolveCallsWithTypedescParams(cl, n)
|
||||
result = if cl.allowMetaTypes: n
|
||||
else: cl.c.semExpr(cl.c, n)
|
||||
if not cl.allowMetaTypes:
|
||||
|
||||
@@ -1837,7 +1837,13 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
# proc foo(T: typedesc, x: T)
|
||||
# when `f` is an unresolved typedesc, `a` could be any
|
||||
# type, so we should not perform this check earlier
|
||||
if a.kind != tyTypeDesc:
|
||||
if c.c.inGenericContext > 0 and
|
||||
a.skipTypes({tyTypeDesc}).kind == tyGenericParam:
|
||||
# generic type bodies can sometimes compile call expressions
|
||||
# prevent unresolved generic parameters from being passed to procs as
|
||||
# typedesc parameters
|
||||
result = isNone
|
||||
elif a.kind != tyTypeDesc:
|
||||
if a.kind == tyGenericParam and tfWildcard in a.flags:
|
||||
# TODO: prevent `a` from matching as a wildcard again
|
||||
result = isGeneric
|
||||
|
||||
@@ -209,6 +209,8 @@ proc iterOverNode(marker: var IntSet, n: PNode, iter: TTypeIter,
|
||||
# a leaf
|
||||
result = iterOverTypeAux(marker, n.typ, iter, closure)
|
||||
else:
|
||||
result = iterOverTypeAux(marker, n.typ, iter, closure)
|
||||
if result: return
|
||||
for i in 0..<n.len:
|
||||
result = iterOverNode(marker, n[i], iter, closure)
|
||||
if result: return
|
||||
@@ -480,6 +482,7 @@ proc valueToString(a: PNode): string =
|
||||
result = $a.intVal
|
||||
of nkFloatLit..nkFloat128Lit: result = $a.floatVal
|
||||
of nkStrLit..nkTripleStrLit: result = a.strVal
|
||||
of nkStaticExpr: result = "static(" & a[0].renderTree & ")"
|
||||
else: result = "<invalid value>"
|
||||
|
||||
proc rangeToStr(n: PNode): string =
|
||||
@@ -1513,7 +1516,7 @@ proc compatibleEffects*(formal, actual: PType): EffectsCompat =
|
||||
|
||||
|
||||
proc isCompileTimeOnly*(t: PType): bool {.inline.} =
|
||||
result = t.kind in {tyTypeDesc, tyStatic}
|
||||
result = t.kind in {tyTypeDesc, tyStatic, tyGenericParam}
|
||||
|
||||
proc containsCompileTimeOnly*(t: PType): bool =
|
||||
if isCompileTimeOnly(t): return true
|
||||
|
||||
@@ -127,42 +127,6 @@ block trefs:
|
||||
|
||||
|
||||
|
||||
block tsharedcases:
|
||||
proc typeNameLen(x: typedesc): int {.compileTime.} =
|
||||
result = x.name.len
|
||||
macro selectType(a, b: typedesc): typedesc =
|
||||
result = a
|
||||
|
||||
type
|
||||
Foo[T] = object
|
||||
data1: array[T.high, int]
|
||||
data2: array[typeNameLen(T), float]
|
||||
data3: array[0..T.typeNameLen, selectType(float, int)]
|
||||
MyEnum = enum A, B, C, D
|
||||
|
||||
var f1: Foo[MyEnum]
|
||||
var f2: Foo[int8]
|
||||
|
||||
doAssert high(f1.data1) == 2 # (D = 3) - 1 == 2
|
||||
doAssert high(f1.data2) == 5 # (MyEnum.len = 6) - 1 == 5
|
||||
|
||||
doAssert high(f2.data1) == 126 # 127 - 1 == 126
|
||||
doAssert high(f2.data2) == 3 # int8.len - 1 == 3
|
||||
|
||||
static:
|
||||
doAssert high(f1.data1) == ord(C)
|
||||
doAssert high(f1.data2) == 5 # length of MyEnum minus one, because we used T.high
|
||||
|
||||
doAssert high(f2.data1) == 126
|
||||
doAssert high(f2.data2) == 3
|
||||
|
||||
doAssert high(f1.data3) == 6 # length of MyEnum
|
||||
doAssert high(f2.data3) == 4 # length of int8
|
||||
|
||||
doAssert f2.data3[0] is float
|
||||
|
||||
|
||||
|
||||
block tmap_auto:
|
||||
let x = map(@[1, 2, 3], x => x+10)
|
||||
doAssert x == @[11, 12, 13]
|
||||
|
||||
72
tests/generics/tuninstantiatedgenericcalls.nim
Normal file
72
tests/generics/tuninstantiatedgenericcalls.nim
Normal file
@@ -0,0 +1,72 @@
|
||||
# Cases that used to only work due to weird workarounds in the compiler
|
||||
# involving not instantiating calls in generic bodies which are removed
|
||||
# due to breaking statics.
|
||||
# The issue was that these calls are compiled as regular expressions at
|
||||
# the generic declaration with unresolved generic parameter types,
|
||||
# which are special cased in some places in the compiler, but sometimes
|
||||
# treated like real types.
|
||||
|
||||
block:
|
||||
type Base10 = object
|
||||
|
||||
func maxLen(T: typedesc[Base10], I: type): int8 =
|
||||
when I is uint8:
|
||||
3
|
||||
elif I is uint16:
|
||||
5
|
||||
elif I is uint32:
|
||||
10
|
||||
elif I is uint64:
|
||||
20
|
||||
else:
|
||||
when sizeof(uint) == 4:
|
||||
10
|
||||
else:
|
||||
20
|
||||
|
||||
type
|
||||
Base10Buf[T: SomeUnsignedInt] = object
|
||||
data: array[maxLen(Base10, T), byte]
|
||||
len: int8
|
||||
|
||||
var x: Base10Buf[uint32]
|
||||
doAssert x.data.len == 10
|
||||
var y: Base10Buf[uint16]
|
||||
doAssert y.data.len == 5
|
||||
|
||||
import typetraits
|
||||
|
||||
block thardcases:
|
||||
proc typeNameLen(x: typedesc): int {.compileTime.} =
|
||||
result = x.name.len
|
||||
macro selectType(a, b: typedesc): typedesc =
|
||||
result = a
|
||||
|
||||
type
|
||||
Foo[T] = object
|
||||
data1: array[T.high, int]
|
||||
data2: array[typeNameLen(T), float]
|
||||
data3: array[0..T.typeNameLen, selectType(float, int)]
|
||||
|
||||
type MyEnum = enum A, B, C, D
|
||||
|
||||
var f1: Foo[MyEnum]
|
||||
var f2: Foo[int8]
|
||||
|
||||
doAssert high(f1.data1) == 2 # (D = 3) - 1 == 2
|
||||
doAssert high(f1.data2) == 5 # (MyEnum.len = 6) - 1 == 5
|
||||
|
||||
doAssert high(f2.data1) == 126 # 127 - 1 == 126
|
||||
doAssert high(f2.data2) == 3 # int8.len - 1 == 3
|
||||
|
||||
static:
|
||||
doAssert high(f1.data1) == ord(C)
|
||||
doAssert high(f1.data2) == 5 # length of MyEnum minus one, because we used T.high
|
||||
|
||||
doAssert high(f2.data1) == 126
|
||||
doAssert high(f2.data2) == 3
|
||||
|
||||
doAssert high(f1.data3) == 6 # length of MyEnum
|
||||
doAssert high(f2.data3) == 4 # length of int8
|
||||
|
||||
doAssert f2.data3[0] is float
|
||||
97
tests/statictypes/tgenericcomputedrange.nim
Normal file
97
tests/statictypes/tgenericcomputedrange.nim
Normal file
@@ -0,0 +1,97 @@
|
||||
import math
|
||||
|
||||
block: # issue #19916
|
||||
type
|
||||
Test[S: static[Natural]] = object
|
||||
a: array[ceilDiv(S, 8), uint8]
|
||||
|
||||
let a = Test[32]()
|
||||
doAssert a.a.len == 4
|
||||
|
||||
block: # issue #20514
|
||||
type Foo[S:static[array[2, int]]] = object
|
||||
values: array[prod(S), float]
|
||||
|
||||
doAssert Foo[[4,5]]().values.len == 20
|
||||
|
||||
block: # issue #20937
|
||||
type
|
||||
Vec3[T: SomeNumber] {.bycopy.} = tuple[x, y, z: T]
|
||||
|
||||
func volume[T](v: Vec3[T]): T =
|
||||
when T is SomeUnsignedInt:
|
||||
v.x * v.y * v.z
|
||||
else:
|
||||
abs (v.x * v.y * v.z)
|
||||
|
||||
type
|
||||
Matrix3[C: static Vec3[uint], T] = object
|
||||
cells: array[C.volume, T]
|
||||
|
||||
let m = Matrix3[(1.uint, 1.uint, 1.uint), uint](cells: [0.uint])
|
||||
doAssert m.cells.len == 1
|
||||
let m2 = Matrix3[(4.uint, 3.uint, 5.uint), uint]()
|
||||
doAssert m2.cells.len == 60
|
||||
|
||||
block: # issue #19284
|
||||
type Board[N, M: static Slice[int]] = array[len(N)*len(M), int8]
|
||||
|
||||
var t: Board[0..4, 0..4]
|
||||
doAssert t.len == 25
|
||||
|
||||
block: # minimal issue #19284
|
||||
proc foo[T](x: T): int =
|
||||
result = 0
|
||||
type Foo[N: static int] = array[0..foo(N), int]
|
||||
|
||||
var t: Foo[5]
|
||||
doAssert t.len == 1
|
||||
|
||||
block:
|
||||
type Foo[T; U: static T] = range[T(0) .. U]
|
||||
|
||||
block:
|
||||
var x: array[Foo[int, 1], int]
|
||||
x[0] = 1
|
||||
x[1] = 2
|
||||
doAssert x == [0: 1, 1: 2]
|
||||
doAssert x is array[0 .. 1, int]
|
||||
|
||||
block:
|
||||
type Bar = enum a, b, c
|
||||
var x: array[Foo[Bar, c], int]
|
||||
x[a] = 1
|
||||
x[b] = 2
|
||||
x[c] = 3
|
||||
doAssert x == [a: 1, b: 2, c: 3]
|
||||
doAssert x is array[a .. c, int]
|
||||
|
||||
block:
|
||||
type Foo[T; U: static T] = array[T(0) .. U, int]
|
||||
|
||||
block:
|
||||
var x: Foo[int, 1]
|
||||
x[0] = 1
|
||||
x[1] = 2
|
||||
doAssert x == [0: 1, 1: 2]
|
||||
doAssert x is array[0 .. 1, int]
|
||||
|
||||
block:
|
||||
type Bar = enum a, b, c
|
||||
var x: Foo[Bar, c]
|
||||
x[a] = 1
|
||||
x[b] = 2
|
||||
x[c] = 3
|
||||
doAssert x == [a: 1, b: 2, c: 3]
|
||||
doAssert x is array[a .. c, int]
|
||||
|
||||
block:
|
||||
type Foo[T; U: static T] = array[T(0) .. U + 1, int]
|
||||
|
||||
block:
|
||||
var x: Foo[int, 1]
|
||||
x[0] = 1
|
||||
x[1] = 2
|
||||
x[2] = 3
|
||||
doAssert x == [0: 1, 1: 2, 2: 3]
|
||||
doAssert x is array[0 .. 2, int]
|
||||
11
tests/statictypes/tstaticgenericparam.nim
Normal file
11
tests/statictypes/tstaticgenericparam.nim
Normal file
@@ -0,0 +1,11 @@
|
||||
# static types that depend on a generic parameter
|
||||
|
||||
block: # issue #19365
|
||||
var ss: seq[string]
|
||||
proc f[T](x: static T) =
|
||||
ss.add($x & ": " & $T)
|
||||
|
||||
f(123)
|
||||
doAssert ss == @["123: int"]
|
||||
f("abc")
|
||||
doAssert ss == @["123: int", "abc: string"]
|
||||
Reference in New Issue
Block a user