fix wrong subtype relation in tuples & infer some conversions (#23228)

fixes #18125

Previously a tuple type like `(T, int)` would match an expected tuple
type `(U, int)` if `T` is a subtype of `U`. This is wrong since the
codegen does not handle type conversions of individual tuple elements in
a type conversion of an entire tuple. For this reason the compiler
already does not accept `(float, int)` for a matched type `(int, int)`,
however the code that checked for which relations are unacceptable
checked for `< isSubtype` rather than `<= isSubtype`, so subtypes were
not included in the unacceptable relations.

Update: Now only considered unacceptable when inheritance is used, as in
[`paramTypesMatch`](3379d26629/compiler/sigmatch.nim (L2252-L2254)).
Ideally subtype relations that don't need conversions, like `nil`,
`seq[empty]`, `range[0..5]` etc would be their own relation
`isConcreteSubtype` (which would also allow us to differentiate with
`openArray[T]`), but this is too big of a refactor for now.

To compensate for this making things like `let x: (Parent, int) =
(Child(), 0)` not compile (they would crash codegen before anyway but
should still work in principle), type inference for tuple constructors
is updated such that they call `fitNode` on the fields and their
expected types, so a type conversion is generated for the individual
subtype element.
This commit is contained in:
metagn
2024-01-18 23:19:29 +03:00
committed by GitHub
parent 3ab8b6b2cf
commit cfd69bad1a
4 changed files with 55 additions and 3 deletions

View File

@@ -2074,12 +2074,10 @@ proc semYield(c: PContext, n: PNode): PNode =
if c.p.owner == nil or c.p.owner.kind != skIterator:
localError(c.config, n.info, errYieldNotAllowedHere)
elif n[0].kind != nkEmpty:
n[0] = semExprWithType(c, n[0]) # check for type compatibility:
var iterType = c.p.owner.typ
let restype = iterType[0]
n[0] = semExprWithType(c, n[0], {}, restype) # check for type compatibility:
if restype != nil:
if restype.kind != tyUntyped:
n[0] = fitNode(c, restype, n[0], n.info)
if n[0].typ == nil: internalError(c.config, n.info, "semYield")
if resultTypeIsInferrable(restype):
@@ -2087,6 +2085,8 @@ proc semYield(c: PContext, n: PNode): PNode =
iterType[0] = inferred
if c.p.resultSym != nil:
c.p.resultSym.typ = inferred
else:
n[0] = fitNode(c, restype, n[0], n.info)
semYieldVarResult(c, n, restype)
else:
@@ -2767,6 +2767,12 @@ proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType
# can check if field name matches expected type here
let expectedElemType = if expected != nil: expected[i] else: nil
n[i][1] = semExprWithType(c, n[i][1], {}, expectedElemType)
if expectedElemType != nil and
(expectedElemType.kind != tyNil and not hasEmpty(expectedElemType)):
# hasEmpty/nil check is to not break existing code like
# `const foo = [(1, {}), (2, {false})]`,
# `const foo = if true: (0, nil) else: (1, new(int))`
n[i][1] = fitNode(c, expectedElemType, n[i][1], n[i][1].info)
if n[i][1].typ.kind == tyTypeDesc:
localError(c.config, n[i][1].info, "typedesc not allowed as tuple field.")
@@ -2793,6 +2799,12 @@ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedT
for i in 0..<n.len:
let expectedElemType = if expected != nil: expected[i] else: nil
n[i] = semExprWithType(c, n[i], {}, expectedElemType)
if expectedElemType != nil and
(expectedElemType.kind != tyNil and not hasEmpty(expectedElemType)):
# hasEmpty/nil check is to not break existing code like
# `const foo = [(1, {}), (2, {false})]`,
# `const foo = if true: (0, nil) else: (1, new(int))`
n[i] = fitNode(c, expectedElemType, n[i], n[i].info)
addSonSkipIntLit(typ, n[i].typ, c.idgen)
result.typ = typ

View File

@@ -587,8 +587,14 @@ proc recordRel(c: var TCandidate, f, a: PType, flags: TTypeRelFlags): TTypeRelat
let firstField = if f.kind == tyTuple: 0
else: 1
for _, ff, aa in tupleTypePairs(f, a):
let oldInheritancePenalty = c.inheritancePenalty
var m = typeRel(c, ff, aa, flags)
if m < isSubtype: return isNone
if m == isSubtype and c.inheritancePenalty > oldInheritancePenalty:
# we can't process individual element type conversions from a
# type conversion for the whole tuple
# subtype relations need type conversions when inheritance is used
return isNone
result = minRel(result, m)
if f.n != nil and a.n != nil:
for i in 0..<f.n.len:

14
tests/tuples/t18125_1.nim Normal file
View File

@@ -0,0 +1,14 @@
# issue #18125 solved with type inference
type
Parent = ref object of RootObj
Child = ref object of Parent
c: char
func foo(c: char): (Parent, int) =
# Works if you use (Parent(Child(c: c)), 0)
(Child(c: c), 0)
let x = foo('x')[0]
doAssert Child(x).c == 'x'

20
tests/tuples/t18125_2.nim Normal file
View File

@@ -0,0 +1,20 @@
discard """
errormsg: "type mismatch: got <(Child, int)> but expected '(Parent, int)'"
line: 17
"""
# issue #18125 solved with correct type relation
type
Parent = ref object of RootObj
Child = ref object of Parent
c: char
func foo(c: char): (Parent, int) =
# Works if you use (Parent(Child(c: c)), 0)
let x = (Child(c: c), 0)
x
let x = foo('x')[0]
doAssert Child(x).c == 'x'