Skip to content

Commit

Permalink
fix object subtype-relation between generic instances (#847)
Browse files Browse the repository at this point in the history
## Summary

Fix a bug with the subtype-of-generic-object logic that caused generic
instances of object-like types to be treated as unrelated when one of
their in-between types is a `ref`/`ptr` type while the formal type is
not (or vice versa). Refer to the added test for an example of where
this issue surfaced.

## Details

The mistake was considering the skipped ptr/ref at each inheritance
step, while it must only be considered once for the formal and original
actual type. This properly considers `ref` and `ptr` types being used
as object base types.

In addition to the fix, the documentation of and inside
`isSubtypeOfGenericInstance` is slightly improved.
  • Loading branch information
zerbina authored Aug 18, 2023
1 parent 255c9a6 commit 802e1aa
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 7 deletions.
17 changes: 11 additions & 6 deletions compiler/sem/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -475,19 +475,23 @@ proc isSubtypeOfGenericInstance(c: var TCandidate; a, f: PType, fGenericOrigin:
## where `f` is a ``tyGenericInst``. The inheritance depth is returned,
## or, if the types are not related, -1.
##
## In case of a match, the unresolved generic parameters of `f` are bound to
## the arguments taken from the base type of actual that matched.
## In case of a subtype relationship existing, the unbound generic parameters
## of `f` are bound to the respective parameters of `a`.
assert f.kind == tyGenericInst
var
askip = skippedNone
fskip = skippedNone

t = a.skipToObject(askip)
last = t ## the unskipped type
last = t ## the most recently compared unskipped type

assert t != nil, "'a' is not object-like type"
discard f.skipToObject(fskip) # only compute the skip kind

if fskip != askip:
# one is a ref|ptr object while the other is not -> no relationship
return -1

proc isEqual(c: var TCandidate, f, a: PType): bool {.nimcall.} =
if f.id == a.id: # fast equality check
result = true
Expand All @@ -507,15 +511,16 @@ proc isSubtypeOfGenericInstance(c: var TCandidate; a, f: PType, fGenericOrigin:

# traverse the type hieararchy until we either reach the end or find a type
# that is equal to `f`
while t != nil and askip == fskip and not isEqual(c, f, last):
while t != nil and not isEqual(c, f, last):
t = t.base
if t.isNil:
break # we reached the end
last = t
t = t.skipToObject(askip)
var skip: SkippedPtr # ignore skip
t = t.skipToObject(skip)
inc result

if t != nil and askip == fskip:
if t != nil:
genericParamPut(c, last, f)
else:
result = -1 # no relationship
Expand Down
23 changes: 22 additions & 1 deletion tests/typerel/tphantom_type_as_base.nim
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,25 @@ block with_ptr_object:

proc p(x: ptr Sub): int = 3
# the more precise overload matches:
doAssert p(addr obj) == 3
doAssert p(addr obj) == 3

block with_intermediate_non_object_base:
# test the case where an intermediate base type is a ``ref`` or
# ``ptr`` while formal type is not
type
Root[T] = object of RootObj

First = ref object of Root[int]
Second = object of First

GFirst[T] = ref object of Root[T]
GSecond = object of GFirst[float]

proc p(x: Root[int]): int = 1
proc p(x: Root[float]): int = 2

# test with non-generic intermediate base:
doAssert p(Second()) == 1

# test with generic intermediate base:
doAssert p(GSecond()) == 2

0 comments on commit 802e1aa

Please sign in to comment.