diff options
-rw-r--r-- | compiler/semcall.nim | 5 | ||||
-rw-r--r-- | compiler/semexprs.nim | 5 | ||||
-rw-r--r-- | compiler/seminst.nim | 3 | ||||
-rw-r--r-- | compiler/semtypes.nim | 19 | ||||
-rw-r--r-- | compiler/semtypinst.nim | 46 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 8 | ||||
-rw-r--r-- | compiler/types.nim | 5 | ||||
-rw-r--r-- | tests/generics/tgenerics_various.nim | 36 | ||||
-rw-r--r-- | tests/generics/tuninstantiatedgenericcalls.nim | 72 | ||||
-rw-r--r-- | tests/statictypes/tgenericcomputedrange.nim | 97 | ||||
-rw-r--r-- | tests/statictypes/tstaticgenericparam.nim | 11 |
11 files changed, 239 insertions, 68 deletions
diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 8445b25e7..c0317f4d6 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -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}) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 53c0790fe..3b40425cf 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -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 diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 3afbedec7..b4b5ad0a2 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -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': diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 14ad72fca..a9fc980db 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -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] diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 50dba515d..7c9bf9039 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -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 @@ -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: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index c3d524791..d6740a7e8 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -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 diff --git a/compiler/types.nim b/compiler/types.nim index 353c445a6..4a7514370 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -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 diff --git a/tests/generics/tgenerics_various.nim b/tests/generics/tgenerics_various.nim index 4cfa47809..aff851981 100644 --- a/tests/generics/tgenerics_various.nim +++ b/tests/generics/tgenerics_various.nim @@ -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] diff --git a/tests/generics/tuninstantiatedgenericcalls.nim b/tests/generics/tuninstantiatedgenericcalls.nim new file mode 100644 index 000000000..c4d95a0fb --- /dev/null +++ b/tests/generics/tuninstantiatedgenericcalls.nim @@ -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 diff --git a/tests/statictypes/tgenericcomputedrange.nim b/tests/statictypes/tgenericcomputedrange.nim new file mode 100644 index 000000000..01ec4767a --- /dev/null +++ b/tests/statictypes/tgenericcomputedrange.nim @@ -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] diff --git a/tests/statictypes/tstaticgenericparam.nim b/tests/statictypes/tstaticgenericparam.nim new file mode 100644 index 000000000..3c77882d1 --- /dev/null +++ b/tests/statictypes/tstaticgenericparam.nim @@ -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"] |