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:
metagn
2023-06-13 21:05:44 +03:00
committed by GitHub
parent 606e9941d0
commit 894a19c6ed
11 changed files with 237 additions and 66 deletions

View File

@@ -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})

View File

@@ -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

View File

@@ -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':

View File

@@ -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]

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View 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

View 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]

View 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"]