Skip to content

Commit 7f0e074

Browse files
authored
generally disallow recursive structural types, check proc param types (#24893)
fixes #5631, fixes #8938, fixes #18855, fixes #19271, fixes #23885, fixes #24877 `isTupleRecursive`, previously only called to give an error for illegal recursions for: * tuple fields * types declared in type sections * explicitly instantiated generic types did not check for recursions in proc types. It now does, meaning proc types now need a nominal type layer to recurse over themselves. It is renamed to `isRecursiveStructuralType` to better reflect what it does, it is different from a recursive type that cannot exist due to a lack of pointer indirection which is possible for nominal types. It is now also called to check the param/return types of procs, similar to how tuple field types are checked. Pointer indirection checks are not needed since procs are pointers. I wondered if this would lead to a slowdown in the compiler but since it only skips structural types it shouldn't take too many iterations, not to mention only proc types are newly considered and aren't that common. But maybe something in the implementation could be inefficient, like the cycle detector using an IntSet. Note: The name `isRecursiveStructuralType` is not exactly correct because it still checks for `distinct` types. If it didn't, then the compiler would accept this: ```nim type A = distinct B B = ref A ``` But this breaks when attempting to write `var x: A`. However this is not the case for: ```nim type A = object x: B B = ref A ``` So a better description would be "types that are structural on the backend". A future step to deal with #14015 and #23224 might be to check the arguments of `tyGenericInst` as well but I don't know if this makes perfect sense.
1 parent 9c25934 commit 7f0e074

10 files changed

+135
-10
lines changed

compiler/seminst.nim

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
308308
param.typ = result[i]
309309

310310
result.n[i] = newSymNode(param)
311+
if isRecursiveStructuralType(result[i]):
312+
localError(c.config, originalParams[i].sym.info, "illegal recursion in type '" & typeToString(result[i]) & "'")
311313
propagateToOwner(result, result[i])
312314
addDecl(c, param)
313315

@@ -318,6 +320,8 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
318320
cl.isReturnType = false
319321
result.n[0] = originalParams[0].copyTree
320322
if result[0] != nil:
323+
if isRecursiveStructuralType(result[0]):
324+
localError(c.config, originalParams[0].info, "illegal recursion in type '" & typeToString(result[0]) & "'")
321325
propagateToOwner(result, result[0])
322326

323327
eraseVoidParams(result)

compiler/semtypes.nim

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
576576
styleCheckDef(c, a[j].info, field)
577577
onDef(field.info, field)
578578
if result.n.len == 0: result.n = nil
579-
if isTupleRecursive(result):
579+
if isRecursiveStructuralType(result):
580580
localError(c.config, n.info, errIllegalRecursionInTypeX % typeToString(result))
581581

582582
proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
@@ -1500,6 +1500,8 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
15001500
if isType: localError(c.config, a.info, "':' expected")
15011501
if kind in {skTemplate, skMacro}:
15021502
typ = newTypeS(tyUntyped, c)
1503+
elif isRecursiveStructuralType(typ):
1504+
localError(c.config, a[^2].info, errIllegalRecursionInTypeX % typeToString(typ))
15031505
elif skipTypes(typ, {tyGenericInst, tyAlias, tySink}).kind == tyVoid:
15041506
continue
15051507

@@ -1563,7 +1565,9 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
15631565
if r != nil:
15641566
# turn explicit 'void' return type into 'nil' because the rest of the
15651567
# compiler only checks for 'nil':
1566-
if skipTypes(r, {tyGenericInst, tyAlias, tySink}).kind != tyVoid:
1568+
if isRecursiveStructuralType(r):
1569+
localError(c.config, n.info, errIllegalRecursionInTypeX % typeToString(r))
1570+
elif skipTypes(r, {tyGenericInst, tyAlias, tySink}).kind != tyVoid:
15671571
if kind notin {skMacro, skTemplate} and r.kind in {tyTyped, tyUntyped}:
15681572
localError(c.config, n[0].info, "return type '" & typeToString(r) &
15691573
"' is only valid for macros and templates")
@@ -1751,7 +1755,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
17511755
# special check for generic object with
17521756
# generic/partial specialized parent
17531757
let tx = result.skipTypes(abstractPtrs, 50)
1754-
if tx.isNil or isTupleRecursive(tx):
1758+
if tx.isNil or isRecursiveStructuralType(tx):
17551759
localError(c.config, n.info, "illegal recursion in type '$1'" % typeToString(result[0]))
17561760
return errorType(c)
17571761
if tx != result and tx.kind == tyObject:

compiler/semtypinst.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ proc checkConstructedType*(conf: ConfigRef; info: TLineInfo, typ: PType) =
2828
if t.kind in tyTypeClasses: discard
2929
elif t.kind in {tyVar, tyLent} and t.elementType.kind in {tyVar, tyLent}:
3030
localError(conf, info, "type 'var var' is not allowed")
31-
elif computeSize(conf, t) == szIllegalRecursion or isTupleRecursive(t):
31+
elif computeSize(conf, t) == szIllegalRecursion or isRecursiveStructuralType(t):
3232
localError(conf, info, "illegal recursion in type '" & typeToString(t) & "'")
3333

3434
proc searchInstTypes*(g: ModuleGraph; key: PType): PType =

compiler/types.nim

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1897,7 +1897,7 @@ proc typeMismatch*(conf: ConfigRef; info: TLineInfo, formal, actual: PType, n: P
18971897
processPragmaAndCallConvMismatch(msg, a, b, conf)
18981898
localError(conf, info, msg)
18991899

1900-
proc isTupleRecursive(t: PType, cycleDetector: var IntSet): bool =
1900+
proc isRecursiveStructuralType(t: PType, cycleDetector: var IntSet): bool =
19011901
if t == nil:
19021902
return false
19031903
if cycleDetector.containsOrIncl(t.id):
@@ -1908,19 +1908,30 @@ proc isTupleRecursive(t: PType, cycleDetector: var IntSet): bool =
19081908
var cycleDetectorCopy: IntSet
19091909
for a in t.kids:
19101910
cycleDetectorCopy = cycleDetector
1911-
if isTupleRecursive(a, cycleDetectorCopy):
1911+
if isRecursiveStructuralType(a, cycleDetectorCopy):
1912+
return true
1913+
of tyProc:
1914+
result = false
1915+
var cycleDetectorCopy: IntSet
1916+
if t.returnType != nil:
1917+
cycleDetectorCopy = cycleDetector
1918+
if isRecursiveStructuralType(t.returnType, cycleDetectorCopy):
1919+
return true
1920+
for _, a in t.paramTypes:
1921+
cycleDetectorCopy = cycleDetector
1922+
if isRecursiveStructuralType(a, cycleDetectorCopy):
19121923
return true
19131924
of tyRef, tyPtr, tyVar, tyLent, tySink,
19141925
tyArray, tyUncheckedArray, tySequence, tyDistinct:
1915-
return isTupleRecursive(t.elementType, cycleDetector)
1926+
return isRecursiveStructuralType(t.elementType, cycleDetector)
19161927
of tyAlias, tyGenericInst:
1917-
return isTupleRecursive(t.skipModifier, cycleDetector)
1928+
return isRecursiveStructuralType(t.skipModifier, cycleDetector)
19181929
else:
19191930
return false
19201931

1921-
proc isTupleRecursive*(t: PType): bool =
1932+
proc isRecursiveStructuralType*(t: PType): bool =
19221933
var cycleDetector = initIntSet()
1923-
isTupleRecursive(t, cycleDetector)
1934+
isRecursiveStructuralType(t, cycleDetector)
19241935

19251936
proc isException*(t: PType): bool =
19261937
# check if `y` is object type and it inherits from Exception

tests/errmsgs/trecursiveproctype1.nim

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
discard """
2+
errormsg: "illegal recursion in type 'Behavior'"
3+
line: 10
4+
"""
5+
6+
# issue #5631
7+
8+
type
9+
Behavior = proc(): Effect
10+
Effect = proc(behavior: Behavior): Behavior

tests/errmsgs/trecursiveproctype2.nim

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
discard """
2+
errormsg: "illegal recursion in type 'B'"
3+
line: 9
4+
"""
5+
6+
# issue #8938
7+
8+
type
9+
A = proc(acc, x: int, y: B): int
10+
B = proc(acc, x: int, y: A): int
11+
12+
proc fact(n: int): int =
13+
proc g(acc, a: int, b: proc(acc, a: int, b: A): int): A =
14+
if a == 0:
15+
acc
16+
else:
17+
b(a * acc, a - 1, b)
18+
g(1, n, g)

tests/errmsgs/trecursiveproctype3.nim

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
discard """
2+
errormsg: "illegal recursion in type 'ptr MyFunc'"
3+
line: 9
4+
"""
5+
6+
# issue #19271
7+
8+
type
9+
MyFunc = proc(f: ptr MyFunc)

tests/errmsgs/trecursiveproctype4.nim

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
discard """
2+
errormsg: "illegal recursion in type 'BB'"
3+
line: 9
4+
"""
5+
6+
# issue #23885
7+
8+
type
9+
EventHandler = proc(target: BB)
10+
BB = (EventHandler,)

tests/errmsgs/trecursiveproctype5.nim

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
discard """
2+
errormsg: "illegal recursion in type 'seq[Shape[system.float32]]"
3+
line: 20
4+
"""
5+
6+
# issue #24877
7+
8+
type
9+
ValT = float32|float64
10+
Square[T: ValT] = object
11+
inner: seq[Shape[T]]
12+
Circle[T: ValT] = object
13+
inner: seq[Shape[T]]
14+
15+
InnerShapesProc[T: ValT] = proc(): seq[Shape[T]]
16+
Shape[T: ValT] = tuple[
17+
innerShapes: InnerShapesProc[T],
18+
]
19+
20+
func newSquare[T: ValT](inner: seq[Shape[T]] = @[]): Square[T] =
21+
Square[T](inner: inner)
22+
23+
proc innerShapes[T: ValT](sq: Square[T]): seq[Shape[T]] = sq.inner
24+
proc iInnerShapes[T: ValT](sq: Square[T]): InnerShapesProc[T] =
25+
proc(): seq[Shape[T]] = sq.innerShapes()
26+
27+
func toShape[T: ValT](sq: Square[T]): Shape[T] =
28+
(innerShapes: sq.iInnerShapes())
29+
30+
func newCircle[T: ValT](inner: seq[Shape[T]] = @[]): Circle[T] =
31+
Circle[T](inner: inner)
32+
33+
proc innerShapes[T: ValT](c: Circle[T]): seq[Shape[T]] = c.inner
34+
proc iInnerShapes[T: ValT](c: Circle[T]): InnerShapesProc[T] =
35+
proc(): seq[Shape[T]] = c.innerShapes()
36+
37+
func toShape[T: ValT](c: Circle[T]): Shape[T] =
38+
(innerShapes: c.iInnerShapes())
39+
40+
const
41+
sq1 = newSquare[float32]()
42+
sq2 = newSquare[float32]()
43+
sq3 = newSquare[float64]()
44+
c1 = newCircle[float64](@[sq3])
45+
c2 = newCircle[float32](@[sq1, sq2])
46+
47+
let
48+
shapes32 = @[sq1.toShape, sq2.toShape, c2.toShape]
49+
shapes64 = @[sq3.toShape, c1.toShape]

tests/errmsgs/trecursiveproctype6.nim

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
discard """
2+
errormsg: "illegal recursion in type 'Test"
3+
line: 9
4+
"""
5+
6+
# issue #18855
7+
8+
type
9+
TestProc = proc(a: Test)
10+
Test = Test

0 commit comments

Comments
 (0)