mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 17:04:41 +00:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
14
tests/tuples/t18125_1.nim
Normal 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
20
tests/tuples/t18125_2.nim
Normal 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'
|
||||
Reference in New Issue
Block a user