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