Skip to content

Commit

Permalink
fix subtype match of generic object types (#24430)
Browse files Browse the repository at this point in the history
split from #24425

Matching `tyGenericBody` performs a match on the last child of the
generic body, in this case the uninstantied `tyObject` type. If the
object contains no generic fields, this ends up being the same type as
all instantiated ones, but if it does, a new type is created. This fails
the `sameObjectTypes` check that subtype matching for object types uses.
To fix this, also consider that the pattern type could be the generic
uninstantiated object type of the matched type in subtype matching.
  • Loading branch information
metagn authored Nov 12, 2024
1 parent 45e21ce commit 511ab72
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 2 deletions.
12 changes: 11 additions & 1 deletion compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -642,12 +642,22 @@ proc genericParamPut(c: var TCandidate; last, fGenericOrigin: PType) =
if x == nil:
put(c, fGenericOrigin[i], last[i])

proc isGenericObjectOf(f, a: PType): bool =
## checks if `f` is an unparametrized generic type
## that `a` is an instance of
if not (f.sym != nil and f.sym.typ.kind == tyGenericBody):
# covers the case where `f` is the last child (body) of the `tyGenericBody`
return false
let aRoot = genericRoot(a)
# use sym equality to check if the `tyGenericBody` types are equal
result = aRoot != nil and f.sym == aRoot.sym

proc isObjectSubtype(c: var TCandidate; a, f, fGenericOrigin: PType): int =
var t = a
assert t.kind == tyObject
var depth = 0
var last = a
while t != nil and not sameObjectTypes(f, t):
while t != nil and not (sameObjectTypes(f, t) or isGenericObjectOf(f, t)):
if t.kind != tyObject: # avoid entering generic params etc
return -1
t = t.baseClass
Expand Down
22 changes: 21 additions & 1 deletion compiler/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1938,6 +1938,9 @@ proc isCharArrayPtr*(t: PType; allowPointerToChar: bool): bool =
else:
result = false

proc isRefPtrObject*(t: PType): bool =
t.kind in {tyRef, tyPtr} and tfRefsAnonObj in t.flags

proc nominalRoot*(t: PType): PType =
## the "name" type of a given instance of a nominal type,
## i.e. the type directly associated with the symbol where the root
Expand Down Expand Up @@ -1970,7 +1973,7 @@ proc nominalRoot*(t: PType): PType =
result = result.skipModifier[0]
let val = result.skipModifier
if val.kind in {tyDistinct, tyEnum, tyObject} or
(val.kind in {tyRef, tyPtr} and tfRefsAnonObj in val.flags):
isRefPtrObject(val):
# atomic nominal types, this generic body is attached to them
discard
else:
Expand Down Expand Up @@ -2000,3 +2003,20 @@ proc nominalRoot*(t: PType): PType =
# skips all typeclasses
# is this correct for `concept`?
result = nil

proc genericRoot*(t: PType): PType =
## gets the root generic type (`tyGenericBody`) from `t`,
## if `t` is a generic type or the body of a generic instantiation
case t.kind
of tyGenericBody:
result = t
of tyGenericInst, tyGenericInvocation:
result = t.genericHead
else:
if t.typeInst != nil:
result = t.typeInst.genericHead
elif t.sym != nil and t.sym.typ.kind == tyGenericBody:
# can happen if `t` is the last child (body) of the generic body
result = t.sym.typ
else:
result = nil
18 changes: 18 additions & 0 deletions tests/objects/tgenericsubtype.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
block: # has generic field
type
Foo[T] = object of RootObj
x: T
Bar = object of Foo[int]

proc foo(x: typedesc[Foo]) = discard

foo(Bar)

block: # no generic field
type
Foo[T] = object of RootObj
Bar = object of Foo[int]

proc foo(x: typedesc[Foo]) = discard

foo(Bar)

0 comments on commit 511ab72

Please sign in to comment.