Files
Nim/compiler/typeallowed.nim
ringabout 7a82c5920c fixes #25677; fixes #25678; typeAllowedAux to improve flag handling (#25684)
fixes #25677;
fixes #25678

This pull request introduces both a bug fix to the type checking logic
in the compiler and new test cases for lent types involving procedures
and tables. The most significant change is a refinement in how type
flags are handled for procedure and function types in the compiler,
which improves correctness in type allowance checks. Additionally, the
test suite is expanded to cover more complex scenarios with lent types
and table lookups.

**Compiler improvements:**

* Refined the handling of type flags in `typeAllowedAux` for procedure
and function types by introducing `innerFlags`, which removes certain
flags (`taObjField`, `taTupField`, `taIsOpenArray`) before recursing
into parameter and return types. This ensures more accurate type
checking and prevents inappropriate flag propagation.

**Testing enhancements:**

* Added new test blocks in `tests/lent/tlents.nim` to cover lent
procedure types stored in objects and used as table values, including a
function that retrieves such procedures from a table by key.
* Introduced a test case for an object containing a lent procedure
field, ensuring correct behavior when accessing and using these fields.
2026-03-30 15:09:11 +02:00

296 lines
10 KiB
Nim

#
#
# The Nim Compiler
# (c) Copyright 2020 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module contains 'typeAllowed' and friends which check
## for invalid types like `openArray[var int]`.
import ast, renderer, options, semdata, types
import std/intsets
when defined(nimPreviewSlimSystem):
import std/assertions
type
TTypeAllowedFlag* = enum
taTupField, # field of a tuple
taObjField, # field of an object
taHeap,
taConcept,
taIsOpenArray,
taNoUntyped
taIsTemplateOrMacro
taProcContextIsNotMacro
taIsCastable
taIsDefaultField
taVoid # only allow direct void fields of objects/tuples
TTypeAllowedFlags* = set[TTypeAllowedFlag]
proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind;
c: PContext; flags: TTypeAllowedFlags = {}): PType
proc typeAllowedNode(marker: var IntSet, n: PNode, kind: TSymKind,
c: PContext; flags: TTypeAllowedFlags = {}): PType =
if n != nil:
result = typeAllowedAux(marker, n.typ, kind, c, flags)
if result == nil:
case n.kind
of nkNone..nkNilLit:
discard
else:
#if n.kind == nkRecCase and kind in {skProc, skFunc, skConst}:
# return n[0].typ
for i in 0..<n.len:
let it = n[i]
result = typeAllowedNode(marker, it, kind, c, flags)
if result != nil: break
else:
result = nil
proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
c: PContext; flags: TTypeAllowedFlags = {}): PType =
assert(kind in {skVar, skLet, skConst, skProc, skFunc, skParam, skResult})
# if we have already checked the type, return true, because we stop the
# evaluation if something is wrong:
result = nil
if typ == nil: return nil
if containsOrIncl(marker, typ.id): return nil
var t = skipTypes(typ, abstractInst-{tyTypeDesc, tySink})
let flags = if t.kind == tyVoid: flags else: flags-{taVoid}
case t.kind
of tyVar, tyLent:
if kind in {skProc, skFunc, skConst} and (views notin c.features):
result = t
elif taIsOpenArray in flags:
result = t
elif t.kind == tyLent and (((kind != skResult or taObjField in flags) and views notin c.features) or
(kind == skParam and {taIsCastable, taObjField, taTupField} * flags == {})): # lent cannot be used as parameters.
# except in the cast environment and as the field of an object
result = t
elif isOutParam(t) and kind != skParam:
result = t
else:
var t2 = skipTypes(t.elementType, abstractInst-{tyTypeDesc, tySink})
case t2.kind
of tyVar, tyLent:
if taHeap notin flags: result = t2 # ``var var`` is illegal on the heap
of tyOpenArray:
if (kind != skParam and views notin c.features) or taIsOpenArray in flags: result = t
else: result = typeAllowedAux(marker, t2[0], kind, c, flags+{taIsOpenArray})
of tyUncheckedArray:
if kind != skParam and views notin c.features: result = t
else: result = typeAllowedAux(marker, t2[0], kind, c, flags)
of tySink:
result = t
else:
if kind notin {skParam, skResult} and views notin c.features: result = t
else: result = typeAllowedAux(marker, t2, kind, c, flags)
of tyProc:
if kind in {skVar, skLet, skConst} and taIsTemplateOrMacro in flags:
result = t
else:
if isInlineIterator(typ) and kind in {skVar, skLet, skConst, skParam, skResult}:
# only closure iterators may be assigned to anything.
result = t
let innerFlags = flags - {taObjField, taTupField, taIsOpenArray}
let f = if kind in {skProc, skFunc}: innerFlags+{taNoUntyped} else: innerFlags
for _, a in t.paramTypes:
if result != nil: break
result = typeAllowedAux(marker, a, skParam, c, f)
if result.isNil and t.returnType != nil:
result = typeAllowedAux(marker, t.returnType, skResult, c, innerFlags)
of tyTypeDesc:
if kind in {skVar, skLet, skConst} and taProcContextIsNotMacro in flags:
result = t
else:
# XXX: This is still a horrible idea...
result = nil
of tyUntyped, tyTyped:
if kind notin {skParam, skResult} or taNoUntyped in flags: result = t
of tyIterable:
if kind notin {skParam} or taNoUntyped in flags: result = t
# tyIterable is only for templates and macros.
of tyStatic:
if kind notin {skParam}: result = t
of tyVoid:
if taVoid notin flags: result = t
of tyTypeClasses:
if tfGenericTypeParam in t.flags or taConcept in flags: #or taField notin flags:
discard
elif t.isResolvedUserTypeClass:
result = typeAllowedAux(marker, t.last, kind, c, flags)
elif kind notin {skParam, skResult}:
result = t
of tyGenericBody, tyGenericParam, tyGenericInvocation,
tyNone, tyForward, tyFromExpr:
result = t
of tyNil:
if kind != skConst and kind != skParam: result = t
of tyString, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCstring, tyPointer:
result = nil
of tyOrdinal:
if kind != skParam: result = t
of tyGenericInst, tyDistinct, tyAlias, tyInferred:
result = typeAllowedAux(marker, skipModifier(t), kind, c, flags)
of tyRange:
if skipTypes(t.elementType, abstractInst-{tyTypeDesc}).kind notin
{tyChar, tyEnum, tyInt..tyFloat128, tyInt..tyUInt64, tyRange}: result = t
of tyOpenArray:
# you cannot nest openArrays/sinks/etc.
if (kind != skParam or taIsOpenArray in flags) and views notin c.features:
result = t
else:
result = typeAllowedAux(marker, t.elementType, kind, c, flags+{taIsOpenArray})
of tyVarargs:
# you cannot nest openArrays/sinks/etc.
if kind != skParam or taIsOpenArray in flags:
result = t
else:
result = typeAllowedAux(marker, t.elementType, kind, c, flags+{taIsOpenArray})
of tySink:
# you cannot nest openArrays/sinks/etc.
if kind != skParam or taIsOpenArray in flags or t.elementType.kind in {tySink, tyLent, tyVar}:
result = t
else:
result = typeAllowedAux(marker, t.elementType, kind, c, flags)
of tyUncheckedArray:
if kind != skParam and taHeap notin flags:
result = t
else:
result = typeAllowedAux(marker, elementType(t), kind, c, flags-{taHeap})
of tySequence:
if t.elementType.kind != tyEmpty:
result = typeAllowedAux(marker, t.elementType, kind, c, flags+{taHeap})
elif kind in {skVar, skLet}:
result = t.elementType
of tyArray:
if t.elementType.kind == tyTypeDesc:
result = t.elementType
elif t.elementType.kind != tyEmpty:
result = typeAllowedAux(marker, t.elementType, kind, c, flags)
elif kind in {skVar, skLet}:
result = t.elementType
of tyRef:
if kind == skConst and taIsDefaultField notin flags: result = t
else: result = typeAllowedAux(marker, t.elementType, kind, c, flags+{taHeap})
of tyPtr:
result = typeAllowedAux(marker, t.elementType, kind, c, flags+{taHeap})
of tySet:
result = typeAllowedAux(marker, t.elementType, kind, c, flags)
of tyObject:
if kind in {skProc, skFunc, skConst} and
t.baseClass != nil and taIsDefaultField notin flags:
result = t
else:
let flags = flags+{taObjField, taVoid}
result = typeAllowedAux(marker, t.baseClass, kind, c, flags)
if result.isNil and t.n != nil:
result = typeAllowedNode(marker, t.n, kind, c, flags)
of tyTuple:
let flags = flags+{taTupField, taVoid}
for a in t.kids:
result = typeAllowedAux(marker, a, kind, c, flags)
if result != nil: break
if result.isNil and t.n != nil:
result = typeAllowedNode(marker, t.n, kind, c, flags)
of tyEmpty:
if kind in {skVar, skLet}: result = t
of tyError:
# for now same as error node; we say it's a valid type as it should
# prevent cascading errors:
result = nil
of tyOwned:
if t.hasElementType and t.skipModifier.skipTypes(abstractInst).kind in {tyRef, tyPtr, tyProc}:
result = typeAllowedAux(marker, t.skipModifier, kind, c, flags+{taHeap})
else:
result = t
proc typeAllowed*(t: PType, kind: TSymKind; c: PContext; flags: TTypeAllowedFlags = {}): PType =
# returns 'nil' on success and otherwise the part of the type that is
# wrong!
var marker = initIntSet()
result = typeAllowedAux(marker, t, kind, c, flags)
type
ViewTypeKind* = enum
noView, immutableView, mutableView
proc combine(dest: var ViewTypeKind, b: ViewTypeKind) {.inline.} =
case dest
of noView, mutableView:
dest = b
of immutableView:
if b == mutableView: dest = b
proc classifyViewTypeAux(marker: var IntSet, t: PType): ViewTypeKind
proc classifyViewTypeNode(marker: var IntSet, n: PNode): ViewTypeKind =
case n.kind
of nkSym:
result = classifyViewTypeAux(marker, n.typ)
of nkOfBranch:
result = classifyViewTypeNode(marker, n.lastSon)
else:
result = noView
for child in n:
result.combine classifyViewTypeNode(marker, child)
if result == mutableView: break
proc classifyViewTypeAux(marker: var IntSet, t: PType): ViewTypeKind =
if containsOrIncl(marker, t.id): return noView
case t.kind
of tyVar:
result = mutableView
of tyLent, tyOpenArray, tyVarargs:
result = immutableView
of tyGenericInst, tyDistinct, tyAlias, tyInferred, tySink, tyOwned,
tyUncheckedArray, tySequence, tyArray, tyRef, tyStatic:
result = classifyViewTypeAux(marker, skipModifier(t))
of tyFromExpr:
if t.hasElementType:
result = classifyViewTypeAux(marker, skipModifier(t))
else:
result = noView
of tyTuple:
result = noView
for a in t.kids:
result.combine classifyViewTypeAux(marker, a)
if result == mutableView: break
of tyObject:
result = noView
if t.n != nil:
result = classifyViewTypeNode(marker, t.n)
if t.baseClass != nil:
result.combine classifyViewTypeAux(marker, t.baseClass)
else:
# it doesn't matter what these types contain, 'ptr openArray' is not a
# view type!
result = noView
proc classifyViewType*(t: PType): ViewTypeKind =
var marker = initIntSet()
result = classifyViewTypeAux(marker, t)
proc directViewType*(t: PType): ViewTypeKind =
# does classify 't' without looking recursively into 't'.
case t.kind
of tyVar:
result = mutableView
of tyLent, tyOpenArray:
result = immutableView
of abstractInst-{tyTypeDesc}:
result = directViewType(t.skipModifier)
else:
result = noView
proc requiresInit*(t: PType): bool =
(t.flags * {tfRequiresInit, tfNeedsFullInit, tfNotNil} != {}) or
classifyViewType(t) != noView