Skip to content
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

fix calls in generic bodies, delay typecheck when no overloads match #22029

Merged
merged 11 commits into from
Jun 13, 2023
5 changes: 4 additions & 1 deletion compiler/semcall.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
5 changes: 4 additions & 1 deletion compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
3 changes: 1 addition & 2 deletions compiler/seminst.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down
19 changes: 15 additions & 4 deletions compiler/semtypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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})

Expand All @@ -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})):
Expand Down Expand Up @@ -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)
Expand All @@ -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).
Expand All @@ -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):
Expand Down Expand Up @@ -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]
Expand Down
46 changes: 24 additions & 22 deletions compiler/semtypinst.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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}, {})

for i in 0..<n.safeLen:
n[i] = reResolveCallsWithTypedescParams(cl, n[i])

return n
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])

return n

proc replaceObjBranches(cl: TReplTypeVars, n: PNode): PNode =
result = n
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 7 additions & 1 deletion compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion compiler/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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}
Copy link
Collaborator Author

@metagn metagn Jun 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change exists in this PR for the sole purpose of making T; U: static T work (though might be more correct anyway). In any case the compiler treats static T the same as static foo() and evaluates it as a constant expression which returns static[T]. static[T] on the other hand goes through semStaticType in semtypes which doesn't have a reason to fail with generic params.

One might think to add elif op.id == ord(wStatic): result = semStaticType(...) here instead, but this might not be correct and could break some code and I don't want to complicate this PR.

Edit: I believe this also fixes #15760, will add test case doesn't


proc containsCompileTimeOnly*(t: PType): bool =
if isCompileTimeOnly(t): return true
Expand Down
36 changes: 0 additions & 36 deletions tests/generics/tgenerics_various.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
72 changes: 72 additions & 0 deletions tests/generics/tuninstantiatedgenericcalls.nim
Original file line number Diff line number Diff line change
@@ -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
Loading