Skip to content

Commit a22d52c

Browse files
metagnnarimiran
authored andcommitted
sem all call nodes in generic type bodies + many required fixes (#23983)
fixes #23406, closes #23854, closes #23855 (test code of both compiles but separate issue exists), refs #23432, follows #23411 In generic bodies, previously all regular `nkCall` nodes like `foo(a, b)` were directly treated as generic statements and delayed immediately, but other call kinds like `a.foo(b)`, `foo a, b` etc underwent typechecking before making sure they have to be delayed, as implemented in #22029. Since the behavior for `nkCall` was slightly buggy (as in However the vast majority of calls in generic bodies out there are `nkCall`, and while there isn't a difference in the expected behavior, this exposes many issues with the implementation started in #22029 given how much more code uses it now. The portion of these issues that CI has caught are fixed in this PR but it's possible there are more. 1. Deref expressions, dot expressions and calls to dot expressions now handle and propagate `tyFromExpr`. This is most of the changes in `semexprs`. 2. For deref expressions to work in `typeof`, a new type flag `tfNonConstExpr` is added for `tyFromExpr` that calls `semExprWithType` with `efInTypeof` on the expression instead of `semConstExpr`. This type flag is set for every `tyFromExpr` type of a node that `prepareNode` encounters, so that the node itself isn't evaluated at compile time when just trying to get the type of the node. 3. Unresolved `static` types matching `static` parameters is now treated the same as unresolved generic types matching `typedesc` parameters in generic type bodies, it causes a failed match which delays the call instantiation. 4. `typedesc` parameters now reject all types containing unresolved generic types like `seq[T]`, not just generic param types by themselves. (using `containsGenericType`) 5. `semgnrc` now doesn't leave generic param symbols it encounters in generic type contexts as just identifiers, and instead turns them into symbol nodes. Normally in generic procs, this isn't a problem since the generic param symbols will be provided again at instantiation time (and in fact creating symbol nodes causes issues since `seminst` doesn't actually instantiate proc body node types). But generic types can try to be instantiated early in `sigmatch` which will give an undeclared identifier error when the param is not provided. Nodes in generic types (specifically in `tyFromExpr` which should be the only use for `semGenericStmt`) undergo full generic type instantiation with `prepareNode`, so there is no issue of these symbols remaining as uninstantiated generic types. 6. `prepareNode` now has more logic for which nodes to avoid instantiating. Subscripts and subscripts turned into calls to `[]` by `semgnrc` need to avoid instantiating the first operand, since it may be a generic body type like `Generic` in an expression like `Generic[int]`. Dot expressions cannot instantiate their RHS as it may be a generic proc symbol or even an undeclared identifier for generic param fields, but have to instantiate their LHS, so calls and subscripts need to still instantiate their first node if it's a dot expression. This logic still isn't perfect and needs the same level of detail as in `semexprs` for which nodes can be left as "untyped" for overloading/dot exprs/subscripts to handle, but should handle the majority of cases. Also the `efDetermineType` requirement for which calls become `tyFromExpr` is removed and as a result `efDetermineType` is entirely unused again. (cherry picked from commit ab18962)
1 parent 8bb9823 commit a22d52c

13 files changed

+381
-46
lines changed

compiler/ast.nim

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,8 @@ const
641641
tfGcSafe* = tfThread
642642
tfObjHasKids* = tfEnumHasHoles
643643
tfReturnsNew* = tfInheritable
644+
tfNonConstExpr* = tfExplicitCallConv
645+
## tyFromExpr where the expression shouldn't be evaluated as a static value
644646
skError* = skUnknown
645647

646648
var

compiler/semcall.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode,
677677
candidates)
678678
result = semResolvedCall(c, r, n, flags)
679679
else:
680-
if efDetermineType in flags and c.inGenericContext > 0 and c.matchedConcept == nil:
680+
if c.inGenericContext > 0 and c.matchedConcept == nil:
681681
result = semGenericStmt(c, n)
682682
result.typ = makeTypeFromExpr(c, result.copyTree)
683683
elif efExplain notin flags:

compiler/semexprs.nim

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,7 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
10291029

10301030
if result != nil:
10311031
if result[0].kind != nkSym:
1032-
if not (efDetermineType in flags and c.inGenericContext > 0):
1032+
if not (c.inGenericContext > 0): # see generic context check in semOverloadedCall
10331033
internalError(c.config, "semOverloadedCallAnalyseEffects")
10341034
return
10351035
let callee = result[0].sym
@@ -1135,7 +1135,12 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
11351135
result.transitionSonsKind(nkCall)
11361136
result.flags.incl nfExplicitCall
11371137
for i in 1..<n.len: result.add n[i]
1138-
return semExpr(c, result, flags)
1138+
return semExpr(c, result, flags, expectedType)
1139+
elif n0.typ.kind == tyFromExpr and c.inGenericContext > 0:
1140+
# don't make assumptions, entire expression needs to be tyFromExpr
1141+
result = semGenericStmt(c, n)
1142+
result.typ = makeTypeFromExpr(c, result.copyTree)
1143+
return
11391144
else:
11401145
n[0] = n0
11411146
else:
@@ -1478,17 +1483,17 @@ proc tryReadingGenericParam(c: PContext, n: PNode, i: PIdent, t: PType): PNode =
14781483
of tyTypeParamsHolders:
14791484
result = readTypeParameter(c, t, i, n.info)
14801485
if result == c.graph.emptyNode:
1481-
result = n
1482-
n.typ = makeTypeFromExpr(c, n.copyTree)
1486+
result = semGenericStmt(c, n)
1487+
result.typ = makeTypeFromExpr(c, result.copyTree)
14831488
of tyUserTypeClasses:
14841489
if t.isResolvedUserTypeClass:
14851490
result = readTypeParameter(c, t, i, n.info)
14861491
else:
1487-
n.typ = makeTypeFromExpr(c, copyTree(n))
1488-
result = n
1489-
of tyGenericParam, tyAnything:
1490-
n.typ = makeTypeFromExpr(c, copyTree(n))
1491-
result = n
1492+
result = semGenericStmt(c, n)
1493+
result.typ = makeTypeFromExpr(c, copyTree(result))
1494+
of tyFromExpr, tyGenericParam, tyAnything:
1495+
result = semGenericStmt(c, n)
1496+
result.typ = makeTypeFromExpr(c, copyTree(result))
14921497
else:
14931498
discard
14941499

@@ -1651,18 +1656,20 @@ proc buildOverloadedSubscripts(n: PNode, ident: PIdent): PNode =
16511656
result.add(newIdentNode(ident, n.info))
16521657
for s in n: result.add s
16531658

1654-
proc semDeref(c: PContext, n: PNode): PNode =
1659+
proc semDeref(c: PContext, n: PNode, flags: TExprFlags): PNode =
16551660
checkSonsLen(n, 1, c.config)
16561661
n[0] = semExprWithType(c, n[0])
16571662
let a = getConstExpr(c.module, n[0], c.idgen, c.graph)
16581663
if a != nil:
1659-
if a.kind == nkNilLit:
1664+
if a.kind == nkNilLit and efInTypeof notin flags:
16601665
localError(c.config, n.info, "nil dereference is not allowed")
16611666
n[0] = a
16621667
result = n
16631668
var t = skipTypes(n[0].typ, {tyGenericInst, tyVar, tyLent, tyAlias, tySink, tyOwned})
16641669
case t.kind
1665-
of tyRef, tyPtr: n.typ = t.lastSon
1670+
of tyRef, tyPtr: n.typ = t.elementType
1671+
of tyMetaTypes, tyFromExpr:
1672+
n.typ = makeTypeFromExpr(c, n.copyTree)
16661673
else: result = nil
16671674
#GlobalError(n[0].info, errCircumNeedsPointer)
16681675

@@ -1692,8 +1699,11 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
16921699
## returns nil if not a built-in subscript operator; also called for the
16931700
## checking of assignments
16941701
if n.len == 1:
1695-
let x = semDeref(c, n)
1702+
let x = semDeref(c, n, flags)
16961703
if x == nil: return nil
1704+
if x.typ.kind == tyFromExpr:
1705+
# depends on generic type
1706+
return x
16971707
result = newNodeIT(nkDerefExpr, x.info, x.typ)
16981708
result.add(x[0])
16991709
return
@@ -3387,7 +3397,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
33873397
result = semArrayConstr(c, n, flags, expectedType)
33883398
of nkObjConstr: result = semObjConstr(c, n, flags, expectedType)
33893399
of nkLambdaKinds: result = semProcAux(c, n, skProc, lambdaPragmas, flags)
3390-
of nkDerefExpr: result = semDeref(c, n)
3400+
of nkDerefExpr: result = semDeref(c, n, flags)
33913401
of nkAddr:
33923402
result = n
33933403
checkSonsLen(n, 1, c.config)

compiler/semgnrc.nim

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,18 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
103103
if s.typ != nil and s.typ.kind == tyStatic:
104104
if s.typ.n != nil:
105105
result = s.typ.n
106+
elif c.inGenericContext > 0 and withinConcept notin flags:
107+
# don't leave generic param as identifier node in generic type,
108+
# sigmatch will try to instantiate generic type AST without all params
109+
# fine to give a symbol node a generic type here since
110+
# we are in a generic context and `prepareNode` will be called
111+
result = newSymNodeTypeDesc(s, c.idgen, n.info)
112+
if canOpenSym(result.sym):
113+
if genericsOpenSym in c.features:
114+
result = newOpenSym(result)
115+
else:
116+
result.flags.incl nfDisabledOpenSym
117+
result.typ = nil
106118
else:
107119
result = n
108120
else:
@@ -127,6 +139,18 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
127139
else:
128140
result.flags.incl nfDisabledOpenSym
129141
result.typ = nil
142+
elif c.inGenericContext > 0 and withinConcept notin flags:
143+
# don't leave generic param as identifier node in generic type,
144+
# sigmatch will try to instantiate generic type AST without all params
145+
# fine to give a symbol node a generic type here since
146+
# we are in a generic context and `prepareNode` will be called
147+
result = newSymNodeTypeDesc(s, c.idgen, n.info)
148+
if canOpenSym(result.sym):
149+
if genericsOpenSym in c.features:
150+
result = newOpenSym(result)
151+
else:
152+
result.flags.incl nfDisabledOpenSym
153+
result.typ = nil
130154
else:
131155
result = n
132156
onUse(n.info, s)

compiler/semtypes.nim

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,6 +1650,10 @@ proc semTypeExpr(c: PContext, n: PNode; prev: PType): PType =
16501650
# unnecessary new type creation
16511651
let alias = maybeAliasType(c, result, prev)
16521652
if alias != nil: result = alias
1653+
elif n.typ.kind == tyFromExpr and c.inGenericContext > 0:
1654+
# sometimes not possible to distinguish type from value in generic body,
1655+
# for example `T.Foo`, so both are handled under `tyFromExpr`
1656+
result = n.typ
16531657
else:
16541658
localError(c.config, n.info, "expected type, but got: " & n.renderTree)
16551659
result = errorType(c)
@@ -2015,11 +2019,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
20152019
elif op.s == "owned" and optOwnedRefs notin c.config.globalOptions and n.len == 2:
20162020
result = semTypeExpr(c, n[1], prev)
20172021
else:
2018-
if c.inGenericContext > 0 and n.kind == nkCall:
2019-
let n = semGenericStmt(c, n)
2020-
result = makeTypeFromExpr(c, n.copyTree)
2021-
else:
2022-
result = semTypeExpr(c, n, prev)
2022+
result = semTypeExpr(c, n, prev)
20232023
of nkWhenStmt:
20242024
var whenResult = semWhen(c, n, false)
20252025
if whenResult.kind == nkStmtList: whenResult.transitionSonsKind(nkStmtListType)

compiler/semtypinst.nim

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,61 @@ proc prepareNode(cl: var TReplTypeVars, n: PNode): PNode =
135135
replaceTypeVarsS(cl, n.sym, result.typ)
136136
else:
137137
replaceTypeVarsS(cl, n.sym, replaceTypeVarsT(cl, n.sym.typ))
138-
let isCall = result.kind in nkCallKinds
139-
# don't try to instantiate symchoice symbols, they can be
140-
# generic procs which the compiler will think are uninstantiated
141-
# because their type will contain uninstantiated params
142-
let isSymChoice = result.kind in nkSymChoices
143-
for i in 0..<n.safeLen:
144-
# XXX HACK: ``f(a, b)``, avoid to instantiate `f`
145-
if isSymChoice or (isCall and i == 0): result.add(n[i])
146-
else: result.add(prepareNode(cl, n[i]))
138+
# we need to avoid trying to instantiate nodes that can have uninstantiated
139+
# types, like generic proc symbols or raw generic type symbols
140+
case n.kind
141+
of nkSymChoices:
142+
# don't try to instantiate symchoice symbols, they can be
143+
# generic procs which the compiler will think are uninstantiated
144+
# because their type will contain uninstantiated params
145+
for i in 0..<n.len:
146+
result.add(n[i])
147+
of nkCallKinds:
148+
# don't try to instantiate call names since they may be generic proc syms
149+
# also bracket expressions can turn into calls with symchoice [] and
150+
# we need to not instantiate the Generic in Generic[int]
151+
# exception exists for the call name being a dot expression since
152+
# dot expressions need their LHS instantiated
153+
assert n.len != 0
154+
let ignoreFirst = n[0].kind != nkDotExpr
155+
let name = n[0].getPIdent
156+
let ignoreSecond = name != nil and name.s == "[]" and n.len > 1 and
157+
(n[1].typ != nil and n[1].typ.kind == tyTypeDesc)
158+
if ignoreFirst:
159+
result.add(n[0])
160+
else:
161+
result.add(prepareNode(cl, n[0]))
162+
if n.len > 1:
163+
if ignoreSecond:
164+
result.add(n[1])
165+
else:
166+
result.add(prepareNode(cl, n[1]))
167+
for i in 2..<n.len:
168+
result.add(prepareNode(cl, n[i]))
169+
of nkBracketExpr:
170+
# don't instantiate Generic body type in expression like Generic[T]
171+
# exception exists for the call name being a dot expression since
172+
# dot expressions need their LHS instantiated
173+
assert n.len != 0
174+
let ignoreFirst = n[0].kind != nkDotExpr and
175+
n[0].typ != nil and n[0].typ.kind == tyTypeDesc
176+
if ignoreFirst:
177+
result.add(n[0])
178+
else:
179+
result.add(prepareNode(cl, n[0]))
180+
for i in 1..<n.len:
181+
result.add(prepareNode(cl, n[i]))
182+
of nkDotExpr:
183+
# don't try to instantiate RHS of dot expression, it can outright be
184+
# undeclared, but definitely instantiate LHS
185+
assert n.len >= 2
186+
result.add(prepareNode(cl, n[0]))
187+
result.add(n[1])
188+
for i in 2..<n.len:
189+
result.add(prepareNode(cl, n[i]))
190+
else:
191+
for i in 0..<n.safeLen:
192+
result.add(prepareNode(cl, n[i]))
147193

148194
proc isTypeParam(n: PNode): bool =
149195
# XXX: generic params should use skGenericParam instead of skType
@@ -226,6 +272,9 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0; expectedType: PT
226272
if n == nil: return
227273
result = copyNode(n)
228274
if n.typ != nil:
275+
if n.typ.kind == tyFromExpr:
276+
# type of node should not be evaluated as a static value
277+
n.typ.flags.incl tfNonConstExpr
229278
result.typ = replaceTypeVarsT(cl, n.typ)
230279
checkMetaInvariants(cl, result.typ)
231280
case n.kind
@@ -596,13 +645,18 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
596645
assert t.n.typ != t
597646
var n = prepareNode(cl, t.n)
598647
if n.kind != nkEmpty:
599-
n = cl.c.semConstExpr(cl.c, n)
648+
if tfNonConstExpr in t.flags:
649+
n = cl.c.semExprWithType(cl.c, n, flags = {efInTypeof})
650+
else:
651+
n = cl.c.semConstExpr(cl.c, n)
600652
if n.typ.kind == tyTypeDesc:
601653
# XXX: sometimes, chained typedescs enter here.
602654
# It may be worth investigating why this is happening,
603655
# because it may cause other bugs elsewhere.
604656
result = n.typ.skipTypes({tyTypeDesc})
605657
# result = n.typ.base
658+
elif tfNonConstExpr in t.flags:
659+
result = n.typ
606660
else:
607661
if n.typ.kind != tyStatic and n.kind != nkType:
608662
# XXX: In the future, semConstExpr should

compiler/sigmatch.nim

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,7 +1860,12 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
18601860
let prev = PType(idTableGet(c.bindings, f))
18611861
if prev == nil:
18621862
if aOrig.kind == tyStatic:
1863-
if f.base.kind notin {tyNone, tyGenericParam}:
1863+
if c.c.inGenericContext > 0 and aOrig.n == nil and not c.isNoCall:
1864+
# don't match unresolved static value to static param to avoid
1865+
# faulty instantiations in calls in generic bodies
1866+
# but not for generic invocations as they only check constraints
1867+
result = isNone
1868+
elif f.base.kind notin {tyNone, tyGenericParam}:
18641869
result = typeRel(c, f.base, a, flags)
18651870
if result != isNone and f.n != nil:
18661871
if not exprStructuralEquivalent(f.n, aOrig.n):
@@ -1917,8 +1922,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
19171922
# proc foo(T: typedesc, x: T)
19181923
# when `f` is an unresolved typedesc, `a` could be any
19191924
# type, so we should not perform this check earlier
1920-
if c.c.inGenericContext > 0 and
1921-
a.skipTypes({tyTypeDesc}).kind == tyGenericParam:
1925+
if c.c.inGenericContext > 0 and a.containsGenericType:
19221926
# generic type bodies can sometimes compile call expressions
19231927
# prevent unresolved generic parameters from being passed to procs as
19241928
# typedesc parameters

tests/generics/t23854.nim

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# issue #23854, not entirely fixed
2+
3+
import std/bitops
4+
5+
const WordBitWidth = sizeof(pointer) * 8
6+
7+
func wordsRequired*(bits: int): int {.inline.} =
8+
const divShiftor = fastLog2(uint32(WordBitWidth))
9+
result = (bits + WordBitWidth - 1) shr divShiftor
10+
11+
type
12+
Algebra* = enum
13+
BLS12_381
14+
15+
BigInt*[bits: static int] = object
16+
limbs*: array[wordsRequired(bits), uint]
17+
18+
Fr*[Name: static Algebra] = object
19+
residue_form*: BigInt[255]
20+
21+
Fp*[Name: static Algebra] = object
22+
residue_form*: BigInt[381]
23+
24+
FF*[Name: static Algebra] = Fp[Name] or Fr[Name]
25+
26+
template getBigInt*[Name: static Algebra](T: type FF[Name]): untyped =
27+
## Get the underlying BigInt type.
28+
typeof(default(T).residue_form)
29+
30+
type
31+
EC_ShortW_Aff*[F] = object
32+
## Elliptic curve point for a curve in Short Weierstrass form
33+
## y² = x³ + a x + b
34+
##
35+
## over a field F
36+
x*, y*: F
37+
38+
type FieldKind* = enum
39+
kBaseField
40+
kScalarField
41+
42+
func bits*[Name: static Algebra](T: type FF[Name]): static int =
43+
T.getBigInt().bits
44+
45+
template getScalarField*(EC: type EC_ShortW_Aff): untyped =
46+
Fr[EC.F.Name]
47+
48+
# ------------------------------------------------------------------------------
49+
50+
type
51+
ECFFT_Descriptor*[EC] = object
52+
## Metadata for FFT on Elliptic Curve
53+
order*: int
54+
rootsOfUnity1*: ptr UncheckedArray[BigInt[EC.getScalarField().bits()]] # Error: in expression 'EC.getScalarField()': identifier expected, but found 'EC.getScalarField'
55+
rootsOfUnity2*: ptr UncheckedArray[BigInt[getScalarField(EC).bits()]] # Compiler SIGSEGV: Illegal Storage Access
56+
57+
func new*(T: type ECFFT_Descriptor): T =
58+
discard
59+
60+
# ------------------------------------------------------------------------------
61+
62+
template getBits[bits: static int](x: ptr UncheckedArray[BigInt[bits]]): int = bits
63+
64+
proc main() =
65+
let ctx = ECFFT_Descriptor[EC_ShortW_Aff[Fp[BLS12_381]]].new()
66+
when false: echo getBits(ctx.rootsOfUnity2) # doesn't work yet?
67+
doAssert ctx.rootsOfUnity1[0].limbs.len == wordsRequired(255)
68+
doAssert ctx.rootsOfUnity2[0].limbs.len == wordsRequired(255)
69+
70+
main()

0 commit comments

Comments
 (0)