Skip to content

Commit 525d64f

Browse files
authored
leave type section symbols unchanged on resem, fix overly general double semcheck for forward types (#24888)
fixes #24887 (really just this [1 line commit](632c7b3) would have been enough to fix the issue but it would ignore the general problem) When a type definition is encountered where the symbol already has a type (not a forward type), the type is left alone (not reset to `tyForward`) and the RHS is handled differently: The RHS is still semchecked, but the type of the symbol is not updated, and nominal type nodes are ignored entirely (specifically if they are the same kind as the symbol's existing type but this restriction is not really needed). If the existing type of the symbol is an enum and and the RHS has a nominal enum type node, the enum fields of the existing type are added to scope rather than creating a new type from the RHS and adding its symbols instead. The goal is to prevent any incompatible nominal types from being generated during resem as in #24887. But it also restricts what macros can do if they generate type section AST, for example if we have: ```nim type Foo = int ``` and a macro modifies the type section while keeping the symbol node for `Foo` like: ```nim type Foo = float ``` Then the type of `Foo` will still remain `int`, while it previously became `float`. While we could maybe allow this and make it so only nominal types cannot be changed, it gets even more complex when considering generic params and whether or not they get updated. So to keep it as simple as possible the rule is that the symbol type does not change, but maybe this behavior was useful for macros. Only nominal type nodes are ignored for semchecking on the RHS, so that cases like this do not cause a regression: ```nim template foo(): untyped = proc bar() {.inject.} = discard int type Foo = foo() bar() # normally works ``` However this specific code exposed a problem with forward type handling: --- In specific cases, when the type section is undergoing the final pass, if the type fits some overly general criteria (it is not an object, enum, alias or a sink type and its node is not a nominal type node), the entire RHS is semchecked for a 2nd time as a standalone type (with `nil` prev) and *maybe* reassigned to the new semchecked type, depending on its type kind. (for some reason including nominal types when we excluded them before?) This causes a redefinition error if the RHS defines a symbol. This code goes all the way back to the first commit and I could not find the reason why it was there, but removing it showed a failure in `thard_tyforward`: If a generic forward type is invoked, it is left as an unresolved `tyGenericInvocation` on the first run. Semchecking it again at the end turns it into a `tyGenericInst`. So my understanding is that it exists to handle these loose forward types, but it is way too general and there is a similar mechanism `c.skipTypes` which is supposed to do the same thing but doesn't. So this is no longer done, and `c.skipTypes` is revamped (and renamed): It is now a list of types and the nodes that are supposed to evaluate to them, such that types needing to be updated later due to containing forward types are added to it along with their nodes. When finishing the type section, these types are reassigned to the semchecked value of their nodes so that the forward types in them are fully resolved. The "reassigning" here works due to updating the data inside the type pointer directly, and is how forward types work by themselves normally (`tyForward` types are modified in place as `s.typ`). For example, as mentioned before, generic invocations of forward types are first created as `tyGenericInvocation` and need to become `tyGenericInst` later. So they are now added to this list along with their node. Object types with forward types as their base types also need to be updated later to check that the base type is correct/inherit fields from it: For this the entire object type and its node are added to the list. Similarly, any case where whether a component type is `tyGenericInst` or `tyGenericInvocation` matters also needs to cascade this (`set` does presumably to check the instantiated type). This is not complete: Generic invocations with forward types only check that their base type is a forward type, but not any of their arguments, which causes #16754 and #24133. The generated invocations also need to cascade properly: `Foo[Bar[ForwardType]]` for example would see that `Bar[ForwardType]` is a generic invocation and stay as a generic invocation itself, but it might not queue itself to be updated later. Even if it did, only the entire type `Foo[Bar[ForwardType]]` needs to be queued, updating `Bar[ForwardType]` by itself would be redundant or it would not change anything at all. But these can be done later.
1 parent 8bc8d40 commit 525d64f

File tree

6 files changed

+141
-37
lines changed

6 files changed

+141
-37
lines changed

compiler/semdata.nim

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@ type
172172
sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index
173173
inUncheckedAssignSection*: int
174174
importModuleLookup*: Table[int, seq[int]] # (module.ident.id, [module.id])
175-
skipTypes*: seq[PNode] # used to skip types between passes in type section. So far only used for inheritance, sets and generic bodies.
175+
forwardTypeUpdates*: seq[(PType, PNode)]
176+
# types that need to be updated in a type section
177+
# due to containing forward types, and their corresponding nodes
176178
inTypeofContext*: int
177179

178180
semAsgnOpr*: proc (c: PContext; n: PNode; k: TNodeKind): PNode {.nimcall.}

compiler/semstmts.nim

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,8 +1460,13 @@ proc typeDefLeftSidePass(c: PContext, typeSection: PNode, i: int) =
14601460
else:
14611461
s = semIdentDef(c, name, skType)
14621462
onDef(name.info, s)
1463-
s.typ = newTypeS(tyForward, c)
1464-
s.typ.sym = s # process pragmas:
1463+
if s.typ != nil:
1464+
# name node is a symbol with a type already, probably in resem, don't touch it
1465+
discard
1466+
else:
1467+
s.typ = newTypeS(tyForward, c)
1468+
s.typ.sym = s
1469+
# process pragmas:
14651470
if name.kind == nkPragmaExpr:
14661471
let rewritten = applyTypeSectionPragmas(c, name[1], typeDef)
14671472
if rewritten != nil:
@@ -1599,7 +1604,26 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
15991604
localError(c.config, a.info, errImplOfXexpected % s.name.s)
16001605
if s.magic != mNone: processMagicType(c, s)
16011606
let oldFlags = s.typ.flags
1602-
if a[1].kind != nkEmpty:
1607+
let preserveSym = s.typ != nil and s.typ.kind != tyForward and sfForward notin s.flags and
1608+
s.magic == mNone # magic might have received type above but still needs processing
1609+
if preserveSym:
1610+
# symbol already has a type, probably in resem, do not modify it
1611+
# but still semcheck the RHS to handle any defined symbols
1612+
# nominal type nodes are still ignored in semtypes
1613+
if a[1].kind != nkEmpty:
1614+
openScope(c)
1615+
pushOwner(c, s)
1616+
a[1] = semGenericParamList(c, a[1], nil)
1617+
inc c.inGenericContext
1618+
discard semTypeNode(c, a[2], s.typ)
1619+
dec c.inGenericContext
1620+
popOwner(c)
1621+
closeScope(c)
1622+
elif a[2].kind != nkEmpty:
1623+
pushOwner(c, s)
1624+
discard semTypeNode(c, a[2], s.typ)
1625+
popOwner(c)
1626+
elif a[1].kind != nkEmpty:
16031627
# We have a generic type declaration here. In generic types,
16041628
# symbol lookup needs to be done here.
16051629
openScope(c)
@@ -1689,7 +1713,7 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
16891713
localError(c.config, name.info, "only a 'distinct' type can borrow `.`")
16901714
let aa = a[2]
16911715
if aa.kind in {nkRefTy, nkPtrTy} and aa.len == 1 and
1692-
aa[0].kind == nkObjectTy:
1716+
aa[0].kind == nkObjectTy and not preserveSym:
16931717
# give anonymous object a dummy symbol:
16941718
var st = s.typ
16951719
if st.kind == tyGenericBody: st = st.typeBodyImpl
@@ -1730,9 +1754,6 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
17301754
obj.flags.incl sfPure
17311755
obj.typ = objTy
17321756
objTy.sym = obj
1733-
for sk in c.skipTypes:
1734-
discard semTypeNode(c, sk, nil)
1735-
c.skipTypes = @[]
17361757

17371758
proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) =
17381759
proc checkMeta(c: PContext; n: PNode; t: PType; hasError: var bool; parent: PType) =
@@ -1768,6 +1789,15 @@ proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) =
17681789
internalAssert c.config, false
17691790

17701791
proc typeSectionFinalPass(c: PContext, n: PNode) =
1792+
for (typ, typeNode) in c.forwardTypeUpdates:
1793+
# types that need to be updated due to containing forward types
1794+
# and their corresponding type nodes
1795+
# for example generic invocations of forward types end up here
1796+
var reified = semTypeNode(c, typeNode, nil)
1797+
assert reified != nil
1798+
assignType(typ, reified)
1799+
typ.itemId = reified.itemId # same id
1800+
c.forwardTypeUpdates = @[]
17711801
for i in 0..<n.len:
17721802
var a = n[i]
17731803
if a.kind == nkCommentStmt: continue
@@ -1794,29 +1824,15 @@ proc typeSectionFinalPass(c: PContext, n: PNode) =
17941824
else:
17951825
while x.kind in {nkStmtList, nkStmtListExpr} and x.len > 0:
17961826
x = x.lastSon
1797-
# we need the 'safeSkipTypes' here because illegally recursive types
1798-
# can enter at this point, see bug #13763
1799-
if x.kind notin {nkObjectTy, nkDistinctTy, nkEnumTy, nkEmpty} and
1800-
s.typ.safeSkipTypes(abstractPtrs).kind notin {tyObject, tyEnum}:
1801-
# type aliases are hard:
1802-
var t = semTypeNode(c, x, nil)
1803-
assert t != nil
1804-
if s.typ != nil and s.typ.kind notin {tyAlias, tySink}:
1805-
if t.kind in {tyProc, tyGenericInst} and not t.isMetaType:
1806-
assignType(s.typ, t)
1807-
s.typ.itemId = t.itemId
1808-
elif t.kind in {tyObject, tyEnum, tyDistinct}:
1809-
assert s.typ != nil
1810-
assignType(s.typ, t)
1811-
s.typ.itemId = t.itemId # same id
18121827
var hasError = false
1813-
let baseType = s.typ.safeSkipTypes(abstractPtrs)
1814-
if baseType.kind in {tyObject, tyTuple} and not baseType.n.isNil and
1815-
(x.kind in {nkObjectTy, nkTupleTy} or
1828+
if x.kind in {nkObjectTy, nkTupleTy} or
18161829
(x.kind in {nkRefTy, nkPtrTy} and x.len == 1 and
1817-
x[0].kind in {nkObjectTy, nkTupleTy})
1818-
):
1819-
checkForMetaFields(c, baseType.n, hasError)
1830+
x[0].kind in {nkObjectTy, nkTupleTy}):
1831+
# we need the 'safeSkipTypes' here because illegally recursive types
1832+
# can enter at this point, see bug #13763
1833+
let baseType = s.typ.safeSkipTypes(abstractPtrs)
1834+
if baseType.kind in {tyObject, tyTuple} and not baseType.n.isNil:
1835+
checkForMetaFields(c, baseType.n, hasError)
18201836
if not hasError:
18211837
checkConstructedType(c.config, s.info, s.typ)
18221838
#instAllTypeBoundOp(c, n.info)

compiler/semtypes.nim

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,31 @@ proc newConstraint(c: PContext, k: TTypeKind): PType =
5959
result.flags.incl tfCheckedForDestructor
6060
result.addSonSkipIntLit(newTypeS(k, c), c.idgen)
6161

62+
proc skipGenericPrev(prev: PType): PType =
63+
result = prev
64+
if prev.kind == tyGenericBody and prev.last.kind != tyNone:
65+
result = prev.last
66+
67+
proc prevIsKind(prev: PType, kind: TTypeKind): bool {.inline.} =
68+
result = prev != nil and skipGenericPrev(prev).kind == kind
69+
6270
proc semEnum(c: PContext, n: PNode, prev: PType): PType =
6371
if n.len == 0: return newConstraint(c, tyEnum)
6472
elif n.len == 1:
6573
# don't create an empty tyEnum; fixes #3052
6674
return errorType(c)
75+
if prevIsKind(prev, tyEnum):
76+
# the symbol already has an enum type (likely resem), don't define a new enum
77+
# but add the enum fields to scope from the original type
78+
let isPure = sfPure in prev.sym.flags
79+
for enumField in prev.n:
80+
assert enumField.kind == nkSym
81+
let e = enumField.sym
82+
if not isPure:
83+
addInterfaceOverloadableSymAt(c, c.currentScope, e)
84+
else:
85+
declarePureEnumField(c, e)
86+
return prev
6787
var
6888
counter, x: BiggestInt = 0
6989
e: PSym = nil
@@ -197,7 +217,7 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType =
197217
if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base)
198218
if base.kind notin {tyGenericParam, tyGenericInvocation}:
199219
if base.kind == tyForward:
200-
c.skipTypes.add n
220+
c.forwardTypeUpdates.add (base, n[1])
201221
elif not isOrdinalType(base, allowEnumWithHoles = true):
202222
localError(c.config, n.info, errOrdinalTypeExpected % typeToString(base, preferDesc))
203223
elif lengthOrd(c.config, base) > MaxSetElements:
@@ -307,6 +327,9 @@ proc addSonSkipIntLitChecked(c: PContext; father, son: PType; it: PNode, id: IdG
307327

308328
proc semDistinct(c: PContext, n: PNode, prev: PType): PType =
309329
if n.len == 0: return newConstraint(c, tyDistinct)
330+
if prevIsKind(prev, tyDistinct):
331+
# the symbol already has a distinct type (likely resem), don't create a new type
332+
return skipGenericPrev(prev)
310333
result = newOrPrevType(tyDistinct, prev, c)
311334
addSonSkipIntLitChecked(c, result, semTypeNode(c, n[0], nil), n[0], c.idgen)
312335
if n.len > 1: result.n = n[1]
@@ -994,11 +1017,15 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
9941017
result = nil
9951018
if n.len == 0:
9961019
return newConstraint(c, tyObject)
1020+
if prevIsKind(prev, tyObject) and sfForward notin prev.sym.flags:
1021+
# the symbol already has an object type (likely resem), don't create a new type
1022+
return skipGenericPrev(prev)
9971023
var check = initIntSet()
9981024
var pos = 0
9991025
var base, realBase: PType = nil
10001026
# n[0] contains the pragmas (if any). We process these later...
10011027
checkSonsLen(n, 3, c.config)
1028+
var needsForwardUpdate = false
10021029
if n[1].kind != nkEmpty:
10031030
realBase = semTypeNode(c, n[1][0], nil)
10041031
base = skipTypesOrNil(realBase, skipPtrs)
@@ -1020,7 +1047,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
10201047
return newType(tyError, c.idgen, result.owner)
10211048

10221049
elif concreteBase.kind == tyForward:
1023-
c.skipTypes.add n #we retry in the final pass
1050+
needsForwardUpdate = true
10241051
else:
10251052
if concreteBase.kind != tyError:
10261053
localError(c.config, n[1].info, "inheritance only works with non-final objects; " &
@@ -1030,6 +1057,10 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
10301057
realBase = nil
10311058
if n.kind != nkObjectTy: internalError(c.config, n.info, "semObjectNode")
10321059
result = newOrPrevType(tyObject, prev, c)
1060+
if needsForwardUpdate:
1061+
# if the inherited object is a forward type,
1062+
# the entire object needs to be checked again
1063+
c.forwardTypeUpdates.add (result, n) #we retry in the final pass
10331064
rawAddSon(result, realBase)
10341065
if realBase == nil and tfInheritable in flags:
10351066
result.flags.incl tfInheritable
@@ -1056,6 +1087,9 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
10561087
if n.len < 1:
10571088
result = newConstraint(c, kind)
10581089
else:
1090+
if prevIsKind(prev, kind) and tfRefsAnonObj in prev.skipTypes({tyGenericBody}).flags:
1091+
# the symbol already has an object type (likely resem), don't create a new type
1092+
return skipGenericPrev(prev)
10591093
let isCall = int ord(n.kind in nkCallKinds+{nkBracketExpr})
10601094
let n = if n[0].kind == nkBracket: n[0] else: n
10611095
checkMinSonsLen(n, 1, c.config)
@@ -1660,6 +1694,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
16601694
for i in 1..<n.len:
16611695
var elem = semGenericParamInInvocation(c, n[i])
16621696
addToResult(elem, true)
1697+
c.forwardTypeUpdates.add (result, n)
16631698
return
16641699
elif t.kind != tyGenericBody:
16651700
# we likely got code of the form TypeA[TypeB] where TypeA is
@@ -1708,7 +1743,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
17081743
localError(c.config, n.info, errCannotInstantiateX % s.name.s)
17091744
result = newOrPrevType(tyError, prev, c)
17101745
elif containsGenericInvocationWithForward(n[0]):
1711-
c.skipTypes.add n #fixes 1500
1746+
c.forwardTypeUpdates.add (result, n) #fixes 1500
17121747
else:
17131748
result = instGenericContainer(c, n.info, result,
17141749
allowMetaTypes = false)

compiler/types.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
526526
let t = typ
527527
if t == nil: return
528528
if prefer in preferToResolveSymbols and t.sym != nil and
529-
sfAnon notin t.sym.flags and t.kind != tySequence:
529+
sfAnon notin t.sym.flags and t.kind notin {tySequence, tyInferred}:
530530
if t.kind == tyInt and isIntLit(t):
531531
if prefer == preferInlayHint:
532532
result = t.sym.name.s

nimsuggest/tests/ttype_highlight.nim

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,4 @@ highlight;;skType;;4;;33;;3
2020
highlight;;skType;;5;;13;;1
2121
highlight;;skType;;6;;25;;5
2222
highlight;;skType;;6;;34;;3
23-
highlight;;skType;;2;;10;;3
24-
highlight;;skType;;3;;11;;3
25-
highlight;;skType;;4;;33;;3
26-
highlight;;skType;;6;;34;;3
2723
"""

tests/types/tresemtypesection.nim

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
discard """
2+
output: '''
3+
NONE
4+
a
5+
NONE
6+
a
7+
'''
8+
"""
9+
10+
# issue #24887
11+
12+
macro foo(x: typed) =
13+
result = x
14+
15+
foo:
16+
type
17+
Flags64 = distinct uint64
18+
19+
const NONE = Flags64(0'u64)
20+
const MAX: Flags64 = Flags64(uint64.high)
21+
22+
proc `$`(x: Flags64): string =
23+
case x:
24+
of NONE:
25+
return "NONE"
26+
of MAX:
27+
return "MAX"
28+
else:
29+
return "UNKNOWN"
30+
31+
let okay = Flags64(128'u64)
32+
33+
echo $NONE
34+
type Foo = ref object
35+
x: int
36+
discard Foo(x: 123)
37+
type Enum = enum a, b, c
38+
echo a
39+
type Bar[T] = object
40+
x: T
41+
discard Bar[int](x: 123)
42+
discard Bar[string](x: "abc")
43+
44+
# regression test:
45+
template templ(): untyped =
46+
proc injected() {.inject.} = discard
47+
int
48+
49+
type TestInject = templ()
50+
var x1: TestInject
51+
injected() # normally works
52+
echo $NONE
53+
echo a
54+
var x2: TestInject
55+
injected()

0 commit comments

Comments
 (0)