This PR allows passing the defining type to generic types in the right
side in a type definition like this:
```nim
type
  Foo = object
    x: Option[Foo]
```
I think generic types should be instanciated after all given arguments
are semchecked,
because generic types can access information about them.
(for example, `Option[T]` in std/option checks if `T` is a pointer like
type)
But in this case, need to instanciate `Option[Foo]` before type of
`Foo.x` is determined.
This commit is contained in:
Tomohiro
2026-02-24 17:39:06 +09:00
committed by GitHub
parent 86b9245dd6
commit fb80f7707d
3 changed files with 78 additions and 2 deletions

View File

@@ -202,7 +202,11 @@ type
tySequence,
tyProc,
tyPointer, tyOpenArray,
tyString, tyCstring, tyForward,
tyString, tyCstring,
tyForward,
# a type not yet semchecked
# When semcheck a type section, all types defined in it are initialized to tyForward
tyInt, tyInt8, tyInt16, tyInt32, tyInt64, # signed integers
tyFloat, tyFloat32, tyFloat64, tyFloat128,
tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64,

View File

@@ -1739,6 +1739,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
var isConcrete = true
let rType = m.call[0].typ
let mIndex = if rType != nil: rType.len - 1 else: -1
var hasForwardTypeParam = false
for i in 1..<m.call.len:
var typ = m.call[i].typ
# is this a 'typedesc' *parameter*? If so, use the typedesc type,
@@ -1755,13 +1756,36 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
skip = false
addToResult(typ, skip)
if typ.kind == tyForward:
hasForwardTypeParam = true
if isConcrete:
if s.ast == nil and s.typ.kind != tyCompositeTypeClass:
# XXX: What kind of error is this? is it still relevant?
localError(c.config, n.info, errCannotInstantiateX % s.name.s)
result = newOrPrevType(tyError, prev, c)
elif containsGenericInvocationWithForward(n[0]):
elif containsGenericInvocationWithForward(n[0]) or hasForwardTypeParam:
# isConcrete == false means this generic type is not instanciated here because it invoked with generic parameters.
# Even if isConcrete == true, don't instanciate it now if there are any `tyForward` type params.
# Such `tyForward` type params will be semchecked later and we can instanciate this next time.
# Some generic types like std/options.Option[T] needs a type kinds of the given type argument.
# return `tyForward` instead of `tyGenericInvocation` because:
# ```nim
# type Foo = object
# x: Option[Foo]
# ```
# returning `tyGenericInvocation` makes `Option[Foo]` to `tyGenericInvocation` and
# next time `semGeneric` is called with `Option[Foo]`, containsGenericType(typeof(`Foo`)) == true
# and `isConcrete == false`.
if prev == nil:
result = newTypeS(tyForward, c)
result.sym = s
else:
assignType(result, newTypeS(tyForward, c))
result.sym = s
c.forwardTypeUpdates.add (result, n) #fixes 1500
return
else:
result = instGenericContainer(c, n.info, result,
allowMetaTypes = false)

View File

@@ -0,0 +1,48 @@
# issue 16754
type
Opt[T] = object
when T is ref:
val: T
x: int
else:
val: T
x: string
type
Foo = ref object
x: Opt[Foo]
Bar = object
x: ref Opt[Bar]
var f = Foo()
assert f.x.x is int
var b = Bar()
assert b.x.x is string
type
BazG[T] = object
x: int
BazGRef[T] = ref object
x: T
Baz = object
x: Opt[BazG[Baz]]
y: Opt[BazGRef[Baz]]
var z = Baz()
assert z.x.x is string
assert z.y.x is int
import options
type
Person = ref object
parent: Option[Person]
proc newPerson(parent: Option[Person]): Person =
Person(parent: parent)
var person = newPerson(none(Person))