mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-11 22:08:54 +00:00
stricter set type match, implicit conversion for literals (#24176)
fixes #18396, fixes #20142 Set types with base types matching less than a generic match (so subrange matches, conversion matches, int conversion matches) are now considered mismatching, as their representation is different on the backends (except VM and JS), causing codegen issues. An exception is granted for set literal types, which now implicitly convert each element to the matched base type, so things like `s == {'a', 'b'}` are still possible where `s` is `set[range['a'..'z']]`. Also every conversion match in this case is unified under the normal "conversion" match, so a literal doesn't match one set type better than the other, unless it's equal. However `{'a', 'b'} == s` or `{'a', 'b'} - s` etc is now not possible. when it used to work in the VM. So this is somewhat breaking, and needs a changelog entry.
This commit is contained in:
@@ -268,6 +268,7 @@ const
|
||||
NotesVerbosity* = computeNotesVerbosity()
|
||||
errXMustBeCompileTime* = "'$1' can only be used in compile-time context"
|
||||
errArgsNeedRunOption* = "arguments can only be given if the '--run' option is selected"
|
||||
errFloatToString* = "cannot convert '$1' to '$2'"
|
||||
|
||||
type
|
||||
TFileInfo* = object
|
||||
|
||||
@@ -497,7 +497,6 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
|
||||
|
||||
const
|
||||
errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters"
|
||||
errFloatToString = "cannot convert '$1' to '$2'"
|
||||
|
||||
proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
|
||||
flags: TExprFlags = {}; expectedType: PType = nil): PNode =
|
||||
|
||||
@@ -1561,11 +1561,12 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
else:
|
||||
result = typeRel(c, f[0], a[0], flags)
|
||||
if result < isGeneric:
|
||||
if result <= isConvertible:
|
||||
result = isNone
|
||||
elif tfIsConstructor notin a.flags:
|
||||
# set constructors are a bit special...
|
||||
if tfIsConstructor notin a.flags:
|
||||
# set['a'..'z'] and set[char] have different representations
|
||||
result = isNone
|
||||
else:
|
||||
# but we can convert individual elements of the constructor
|
||||
result = isConvertible
|
||||
of tyPtr, tyRef:
|
||||
a = reduceToBase(a)
|
||||
if a.kind == f.kind:
|
||||
@@ -2183,6 +2184,81 @@ proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate,
|
||||
else:
|
||||
result.add arg
|
||||
|
||||
proc convertLiteral(kind: TNodeKind, c: PContext, m: TCandidate; n: PNode, newType: PType): PNode =
|
||||
# based off changeType but generates implicit conversions instead
|
||||
template addConsiderNil(s, node) =
|
||||
let val = node
|
||||
if val.isNil: return nil
|
||||
s.add(val)
|
||||
case n.kind
|
||||
of nkCurly:
|
||||
result = copyNode(n)
|
||||
for i in 0..<n.len:
|
||||
if n[i].kind == nkRange:
|
||||
var x = copyNode(n[i])
|
||||
x.addConsiderNil convertLiteral(kind, c, m, n[i][0], elemType(newType))
|
||||
x.addConsiderNil convertLiteral(kind, c, m, n[i][1], elemType(newType))
|
||||
result.add x
|
||||
else:
|
||||
result.addConsiderNil convertLiteral(kind, c, m, n[i], elemType(newType))
|
||||
result.typ = newType
|
||||
return
|
||||
of nkBracket:
|
||||
result = copyNode(n)
|
||||
for i in 0..<n.len:
|
||||
result.addConsiderNil convertLiteral(kind, c, m, n[i], elemType(newType))
|
||||
result.typ = newType
|
||||
return
|
||||
of nkPar, nkTupleConstr:
|
||||
let tup = newType.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct})
|
||||
if tup.kind == tyTuple:
|
||||
result = copyNode(n)
|
||||
if n.len > 0 and n[0].kind == nkExprColonExpr:
|
||||
# named tuple?
|
||||
for i in 0..<n.len:
|
||||
var name = n[i][0]
|
||||
if name.kind != nkSym:
|
||||
#globalError(c.config, name.info, "invalid tuple constructor")
|
||||
return nil
|
||||
if tup.n != nil:
|
||||
var f = getSymFromList(tup.n, name.sym.name)
|
||||
if f == nil:
|
||||
#globalError(c.config, name.info, "unknown identifier: " & name.sym.name.s)
|
||||
return nil
|
||||
result.addConsiderNil convertLiteral(kind, c, m, n[i][1], f.typ)
|
||||
else:
|
||||
result.addConsiderNil convertLiteral(kind, c, m, n[i][1], tup[i])
|
||||
else:
|
||||
for i in 0..<n.len:
|
||||
result.addConsiderNil convertLiteral(kind, c, m, n[i], tup[i])
|
||||
result.typ = newType
|
||||
return
|
||||
of nkCharLit..nkUInt64Lit:
|
||||
if n.kind != nkUInt64Lit and not sameTypeOrNil(n.typ, newType) and isOrdinalType(newType):
|
||||
let value = n.intVal
|
||||
if value < firstOrd(c.config, newType) or value > lastOrd(c.config, newType):
|
||||
return nil
|
||||
result = copyNode(n)
|
||||
result.typ = newType
|
||||
return
|
||||
of nkFloatLit..nkFloat64Lit:
|
||||
if newType.skipTypes(abstractVarRange-{tyTypeDesc}).kind == tyFloat:
|
||||
if not floatRangeCheck(n.floatVal, newType):
|
||||
return nil
|
||||
result = copyNode(n)
|
||||
result.typ = newType
|
||||
return
|
||||
of nkSym:
|
||||
if n.sym.kind == skEnumField and not sameTypeOrNil(n.sym.typ, newType) and isOrdinalType(newType):
|
||||
let value = n.sym.position
|
||||
if value < firstOrd(c.config, newType) or value > lastOrd(c.config, newType):
|
||||
return nil
|
||||
result = copyNode(n)
|
||||
result.typ = newType
|
||||
return
|
||||
else: discard
|
||||
return implicitConv(kind, newType, n, m, c)
|
||||
|
||||
proc isLValue(c: PContext; n: PNode, isOutParam = false): bool {.inline.} =
|
||||
let aa = isAssignable(nil, n)
|
||||
case aa
|
||||
@@ -2394,7 +2470,20 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
|
||||
if f.skipTypes({tyRange}).kind in {tyInt, tyUInt}:
|
||||
inc(m.convMatches)
|
||||
inc(m.convMatches)
|
||||
result = implicitConv(nkHiddenStdConv, f, arg, m, c)
|
||||
if skipTypes(f, abstractVar-{tyTypeDesc}).kind == tySet:
|
||||
if tfIsConstructor in a.flags and arg.kind == nkCurly:
|
||||
# we marked the set as convertible only because the arg is a literal
|
||||
# in which case we individually convert each element
|
||||
let t =
|
||||
if containsGenericType(f):
|
||||
getInstantiatedType(c, arg, m, f).skipTypes({tySink})
|
||||
else:
|
||||
f.skipTypes({tySink})
|
||||
result = convertLiteral(nkHiddenStdConv, c, m, arg, t)
|
||||
else:
|
||||
result = nil
|
||||
else:
|
||||
result = implicitConv(nkHiddenStdConv, f, arg, m, c)
|
||||
of isIntConv:
|
||||
# I'm too lazy to introduce another ``*matches`` field, so we conflate
|
||||
# ``isIntConv`` and ``isIntLit`` here:
|
||||
|
||||
32
tests/sets/trangeincompatible.nim
Normal file
32
tests/sets/trangeincompatible.nim
Normal file
@@ -0,0 +1,32 @@
|
||||
block: # issue #20142
|
||||
let
|
||||
s1: set['a' .. 'g'] = {'a', 'e'}
|
||||
s2: set['a' .. 'g'] = {'b', 'c', 'd', 'f'} # this works fine
|
||||
s3 = {'b', 'c', 'd', 'f'}
|
||||
|
||||
doAssert s1 != s2
|
||||
doAssert s1 == {range['a'..'g'] 'a', 'e'}
|
||||
doAssert s2 == {range['a'..'g'] 'b', 'c', 'd', 'f'}
|
||||
# literal conversion:
|
||||
doAssert s1 == {'a', 'e'}
|
||||
doAssert s2 == {'b', 'c', 'd', 'f'}
|
||||
doAssert s3 == {'b', 'c', 'd', 'f'}
|
||||
doAssert not compiles(s1 == s3)
|
||||
doAssert not compiles(s2 == s3)
|
||||
# can't convert literal 'z', overload match fails
|
||||
doAssert not compiles(s1 == {'a', 'z'})
|
||||
|
||||
block: # issue #18396
|
||||
var s1: set[char] = {'a', 'b'}
|
||||
var s2: set['a'..'z'] = {'a', 'b'}
|
||||
doAssert s1 == {'a', 'b'}
|
||||
doAssert s2 == {range['a'..'z'] 'a', 'b'}
|
||||
doAssert s2 == {'a', 'b'}
|
||||
doAssert not compiles(s1 == s2)
|
||||
|
||||
block: # issue #16270
|
||||
var s1: set[char] = {'a', 'b'}
|
||||
var s2: set['a'..'z'] = {'a', 'c'}
|
||||
doAssert not (compiles do: s2 = s2 + s1)
|
||||
s2 = s2 + {'a', 'b'}
|
||||
doAssert s2 == {'a', 'b', 'c'}
|
||||
@@ -45,7 +45,6 @@ block:
|
||||
TNoteKind = range[k1..k2]
|
||||
var notes: set[TNoteKind]
|
||||
notes = {k0} #[tt.Error
|
||||
^ cannot convert 'k0' to 'TNoteKind = range 1..2(TMsgKind)]#
|
||||
^ type mismatch: got <set[TMsgKind]> but expected 'set[TNoteKind]']#
|
||||
notes = {k0..k3} #[tt.Error
|
||||
^ cannot convert 'k0' to 'TNoteKind = range 1..2(TMsgKind)'; tt.Error
|
||||
^ cannot convert 'k3' to 'TNoteKind = range 1..2(TMsgKind)']#
|
||||
^ type mismatch: got <set[TMsgKind]> but expected 'set[TNoteKind]']#
|
||||
|
||||
Reference in New Issue
Block a user