mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-11 22:08:54 +00:00
make var/pointer types not match if base type has to be converted (#24130)
split again from #24038, fixes
https://github.com/status-im/nimbus-eth2/pull/6554#issuecomment-2354977102
`var`/pointer types are no longer implicitly convertible to each other
if their element types either:
* require an int conversion or another conversion operation as long as
it's not to `openarray`,
* are subtypes with pointer indirection,
Previously any conversion below a subrange match would match if the
element type wasn't a pointer type, then it would error later in
`analyseIfAddressTaken`.
Different from #24038 in that the preview define that made subrange
matches also fail to match is removed for a simpler diff so that it can
be backported.
(cherry picked from commit 1660ddf98a)
This commit is contained in:
@@ -984,9 +984,21 @@ proc inferStaticsInRange(c: var TCandidate,
|
||||
doInferStatic(lowerBound, getInt(upperBound) + 1 - lengthOrd(c.c.config, concrete))
|
||||
|
||||
template subtypeCheck() =
|
||||
if result <= isSubrange and f.lastSon.skipTypes(abstractInst).kind in {
|
||||
tyRef, tyPtr, tyVar, tyLent, tyOwned}:
|
||||
case result
|
||||
of isIntConv:
|
||||
result = isNone
|
||||
of isSubrange:
|
||||
discard # XXX should be isNone with preview define, warnings
|
||||
of isConvertible:
|
||||
if f.lastSon.skipTypes(abstractInst).kind != tyOpenArray:
|
||||
# exclude var openarray which compiler supports
|
||||
result = isNone
|
||||
of isSubtype:
|
||||
if f.lastSon.skipTypes(abstractInst).kind in {
|
||||
tyRef, tyPtr, tyVar, tyLent, tyOwned}:
|
||||
# compiler can't handle subtype conversions with pointer indirection
|
||||
result = isNone
|
||||
else: discard
|
||||
|
||||
proc isCovariantPtr(c: var TCandidate, f, a: PType): bool =
|
||||
# this proc is always called for a pair of matching types
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
discard """
|
||||
errormsg: "for a 'var' type a variable needs to be passed; but 'uint16(x)' is immutable"
|
||||
errormsg: "type mismatch: got <uint8>"
|
||||
"""
|
||||
|
||||
proc toUInt16(x: var uint16) =
|
||||
discard
|
||||
|
||||
var x = uint8(1)
|
||||
toUInt16 x
|
||||
toUInt16 x
|
||||
|
||||
16
tests/int/twrongexplicitvarconv.nim
Normal file
16
tests/int/twrongexplicitvarconv.nim
Normal file
@@ -0,0 +1,16 @@
|
||||
discard """
|
||||
action: reject
|
||||
nimout: '''
|
||||
but expression 'int(a)' is immutable, not 'var'
|
||||
'''
|
||||
"""
|
||||
|
||||
proc `++`(n: var int) =
|
||||
n += 1
|
||||
|
||||
var a: int32 = 15
|
||||
|
||||
++int(a) #[tt.Error
|
||||
^ type mismatch: got <int>]#
|
||||
|
||||
echo a
|
||||
9
tests/int/twrongvarconv.nim
Normal file
9
tests/int/twrongvarconv.nim
Normal file
@@ -0,0 +1,9 @@
|
||||
proc `++`(n: var int) =
|
||||
n += 1
|
||||
|
||||
var a: int32 = 15
|
||||
|
||||
++a #[tt.Error
|
||||
^ type mismatch: got <int32>]#
|
||||
|
||||
echo a
|
||||
145
tests/overload/mvaruintconv.nim
Normal file
145
tests/overload/mvaruintconv.nim
Normal file
@@ -0,0 +1,145 @@
|
||||
import
|
||||
std/[macros, tables, hashes]
|
||||
|
||||
export
|
||||
macros
|
||||
|
||||
type
|
||||
FieldDescription* = object
|
||||
name*: NimNode
|
||||
isPublic*: bool
|
||||
isDiscriminator*: bool
|
||||
typ*: NimNode
|
||||
pragmas*: NimNode
|
||||
caseField*: NimNode
|
||||
caseBranch*: NimNode
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
func isTuple*(t: NimNode): bool =
|
||||
t.kind == nnkBracketExpr and t[0].kind == nnkSym and eqIdent(t[0], "tuple")
|
||||
|
||||
macro isTuple*(T: type): untyped =
|
||||
newLit(isTuple(getType(T)[1]))
|
||||
|
||||
proc collectFieldsFromRecList(result: var seq[FieldDescription],
|
||||
n: NimNode,
|
||||
parentCaseField: NimNode = nil,
|
||||
parentCaseBranch: NimNode = nil,
|
||||
isDiscriminator = false) =
|
||||
case n.kind
|
||||
of nnkRecList:
|
||||
for entry in n:
|
||||
collectFieldsFromRecList result, entry,
|
||||
parentCaseField, parentCaseBranch
|
||||
of nnkRecWhen:
|
||||
for branch in n:
|
||||
case branch.kind:
|
||||
of nnkElifBranch:
|
||||
collectFieldsFromRecList result, branch[1],
|
||||
parentCaseField, parentCaseBranch
|
||||
of nnkElse:
|
||||
collectFieldsFromRecList result, branch[0],
|
||||
parentCaseField, parentCaseBranch
|
||||
else:
|
||||
doAssert false
|
||||
|
||||
of nnkRecCase:
|
||||
collectFieldsFromRecList result, n[0],
|
||||
parentCaseField,
|
||||
parentCaseBranch,
|
||||
isDiscriminator = true
|
||||
|
||||
for i in 1 ..< n.len:
|
||||
let branch = n[i]
|
||||
case branch.kind
|
||||
of nnkOfBranch:
|
||||
collectFieldsFromRecList result, branch[^1], n[0], branch
|
||||
of nnkElse:
|
||||
collectFieldsFromRecList result, branch[0], n[0], branch
|
||||
else:
|
||||
doAssert false
|
||||
|
||||
of nnkIdentDefs:
|
||||
let fieldType = n[^2]
|
||||
for i in 0 ..< n.len - 2:
|
||||
var field: FieldDescription
|
||||
field.name = n[i]
|
||||
field.typ = fieldType
|
||||
field.caseField = parentCaseField
|
||||
field.caseBranch = parentCaseBranch
|
||||
field.isDiscriminator = isDiscriminator
|
||||
|
||||
if field.name.kind == nnkPragmaExpr:
|
||||
field.pragmas = field.name[1]
|
||||
field.name = field.name[0]
|
||||
|
||||
if field.name.kind == nnkPostfix:
|
||||
field.isPublic = true
|
||||
field.name = field.name[1]
|
||||
|
||||
result.add field
|
||||
|
||||
of nnkSym:
|
||||
result.add FieldDescription(
|
||||
name: n,
|
||||
typ: getType(n),
|
||||
caseField: parentCaseField,
|
||||
caseBranch: parentCaseBranch,
|
||||
isDiscriminator: isDiscriminator)
|
||||
|
||||
of nnkNilLit, nnkDiscardStmt, nnkCommentStmt, nnkEmpty:
|
||||
discard
|
||||
|
||||
else:
|
||||
doAssert false, "Unexpected nodes in recordFields:\n" & n.treeRepr
|
||||
|
||||
proc collectFieldsInHierarchy(result: var seq[FieldDescription],
|
||||
objectType: NimNode) =
|
||||
var objectType = objectType
|
||||
|
||||
objectType.expectKind {nnkObjectTy, nnkRefTy}
|
||||
|
||||
if objectType.kind == nnkRefTy:
|
||||
objectType = objectType[0]
|
||||
|
||||
objectType.expectKind nnkObjectTy
|
||||
|
||||
var baseType = objectType[1]
|
||||
if baseType.kind != nnkEmpty:
|
||||
baseType.expectKind nnkOfInherit
|
||||
baseType = baseType[0]
|
||||
baseType.expectKind nnkSym
|
||||
baseType = getImpl(baseType)
|
||||
baseType.expectKind nnkTypeDef
|
||||
baseType = baseType[2]
|
||||
baseType.expectKind {nnkObjectTy, nnkRefTy}
|
||||
collectFieldsInHierarchy result, baseType
|
||||
|
||||
let recList = objectType[2]
|
||||
collectFieldsFromRecList result, recList
|
||||
|
||||
proc recordFields*(typeImpl: NimNode): seq[FieldDescription] =
|
||||
if typeImpl.isTuple:
|
||||
for i in 1 ..< typeImpl.len:
|
||||
result.add FieldDescription(typ: typeImpl[i], name: ident("Field" & $(i - 1)))
|
||||
return
|
||||
|
||||
let objectType = case typeImpl.kind
|
||||
of nnkObjectTy: typeImpl
|
||||
of nnkTypeDef: typeImpl[2]
|
||||
else:
|
||||
macros.error("object type expected", typeImpl)
|
||||
return
|
||||
|
||||
collectFieldsInHierarchy(result, objectType)
|
||||
|
||||
macro field*(obj: typed, fieldName: static string): untyped =
|
||||
newDotExpr(obj, ident fieldName)
|
||||
|
||||
proc skipPragma*(n: NimNode): NimNode =
|
||||
if n.kind == nnkPragmaExpr: n[0]
|
||||
else: n
|
||||
|
||||
|
||||
{.pop.}
|
||||
207
tests/overload/tvaruintconv.nim
Normal file
207
tests/overload/tvaruintconv.nim
Normal file
@@ -0,0 +1,207 @@
|
||||
discard """
|
||||
action: compile
|
||||
"""
|
||||
|
||||
# https://github.com/status-im/nimbus-eth2/pull/6554#issuecomment-2354977102
|
||||
# failed with "for a 'var' type a variable needs to be passed; but 'uint64(result)' is immutable"
|
||||
|
||||
import
|
||||
std/[typetraits, macros]
|
||||
|
||||
type
|
||||
DefaultFlavor = object
|
||||
|
||||
template serializationFormatImpl(Name: untyped) {.dirty.} =
|
||||
type Name = object
|
||||
|
||||
template serializationFormat(Name: untyped) =
|
||||
serializationFormatImpl(Name)
|
||||
|
||||
template setReader(Format, FormatReader: distinct type) =
|
||||
when arity(FormatReader) > 1:
|
||||
template Reader(T: type Format, F: distinct type = DefaultFlavor): type = FormatReader[F]
|
||||
else:
|
||||
template ReaderType(T: type Format): type = FormatReader
|
||||
template Reader(T: type Format): type = FormatReader
|
||||
|
||||
template useDefaultReaderIn(T: untyped, Flavor: type) =
|
||||
mixin Reader
|
||||
|
||||
template readValue(r: var Reader(Flavor), value: var T) =
|
||||
mixin readRecordValue
|
||||
readRecordValue(r, value)
|
||||
|
||||
import mvaruintconv
|
||||
|
||||
type
|
||||
FieldTag[RecordType: object; fieldName: static string] = distinct void
|
||||
|
||||
func declval*(T: type): T {.compileTime.} =
|
||||
default(ptr T)[]
|
||||
|
||||
macro enumAllSerializedFieldsImpl(T: type, body: untyped): untyped =
|
||||
var typeAst = getType(T)[1]
|
||||
var typeImpl: NimNode
|
||||
let isSymbol = not typeAst.isTuple
|
||||
|
||||
if not isSymbol:
|
||||
typeImpl = typeAst
|
||||
else:
|
||||
typeImpl = getImpl(typeAst)
|
||||
result = newStmtList()
|
||||
|
||||
var i = 0
|
||||
for field in recordFields(typeImpl):
|
||||
let
|
||||
fieldIdent = field.name
|
||||
realFieldName = newLit($fieldIdent.skipPragma)
|
||||
fieldName = realFieldName
|
||||
fieldIndex = newLit(i)
|
||||
|
||||
let fieldNameDefs =
|
||||
if isSymbol:
|
||||
quote:
|
||||
const fieldName {.inject, used.} = `fieldName`
|
||||
const realFieldName {.inject, used.} = `realFieldName`
|
||||
else:
|
||||
quote:
|
||||
const fieldName {.inject, used.} = $`fieldIndex`
|
||||
const realFieldName {.inject, used.} = $`fieldIndex`
|
||||
|
||||
let field =
|
||||
if isSymbol:
|
||||
quote do: declval(`T`).`fieldIdent`
|
||||
else:
|
||||
quote do: declval(`T`)[`fieldIndex`]
|
||||
|
||||
result.add quote do:
|
||||
block:
|
||||
`fieldNameDefs`
|
||||
|
||||
template FieldType: untyped {.inject, used.} = typeof(`field`)
|
||||
|
||||
`body`
|
||||
|
||||
# echo repr(result)
|
||||
|
||||
template enumAllSerializedFields(T: type, body): untyped =
|
||||
enumAllSerializedFieldsImpl(T, body)
|
||||
|
||||
type
|
||||
FieldReader[RecordType, Reader] = tuple[
|
||||
fieldName: string,
|
||||
reader: proc (rec: var RecordType, reader: var Reader)
|
||||
{.gcsafe, nimcall.}
|
||||
]
|
||||
|
||||
proc totalSerializedFieldsImpl(T: type): int =
|
||||
mixin enumAllSerializedFields
|
||||
enumAllSerializedFields(T): inc result
|
||||
|
||||
template totalSerializedFields(T: type): int =
|
||||
(static(totalSerializedFieldsImpl(T)))
|
||||
|
||||
template GetFieldType(FT: type FieldTag): type =
|
||||
typeof field(declval(FT.RecordType), FT.fieldName)
|
||||
|
||||
proc makeFieldReadersTable(RecordType, ReaderType: distinct type,
|
||||
numFields: static[int]):
|
||||
array[numFields, FieldReader[RecordType, ReaderType]] =
|
||||
mixin enumAllSerializedFields, handleReadException
|
||||
var idx = 0
|
||||
|
||||
enumAllSerializedFields(RecordType):
|
||||
proc readField(obj: var RecordType, reader: var ReaderType)
|
||||
{.gcsafe, nimcall.} =
|
||||
|
||||
mixin readValue
|
||||
|
||||
type F = FieldTag[RecordType, realFieldName]
|
||||
field(obj, realFieldName) = reader.readValue(GetFieldType(F))
|
||||
|
||||
result[idx] = (fieldName, readField)
|
||||
inc idx
|
||||
|
||||
proc fieldReadersTable(RecordType, ReaderType: distinct type): auto =
|
||||
mixin readValue
|
||||
type T = RecordType
|
||||
const numFields = totalSerializedFields(T)
|
||||
var tbl {.threadvar.}: ref array[numFields, FieldReader[RecordType, ReaderType]]
|
||||
if tbl == nil:
|
||||
tbl = new typeof(tbl)
|
||||
tbl[] = makeFieldReadersTable(RecordType, ReaderType, numFields)
|
||||
return addr(tbl[])
|
||||
|
||||
proc readValue(reader: var auto, T: type): T =
|
||||
mixin readValue
|
||||
reader.readValue(result)
|
||||
|
||||
template decode(Format: distinct type,
|
||||
input: string,
|
||||
RecordType: distinct type): auto =
|
||||
mixin Reader
|
||||
block: # https://github.com/nim-lang/Nim/issues/22874
|
||||
var reader: Reader(Format)
|
||||
reader.readValue(RecordType)
|
||||
|
||||
template readValue(Format: type,
|
||||
ValueType: type): untyped =
|
||||
mixin Reader, init, readValue
|
||||
var reader: Reader(Format)
|
||||
readValue reader, ValueType
|
||||
|
||||
template parseArrayImpl(numElem: untyped,
|
||||
actionValue: untyped) =
|
||||
actionValue
|
||||
|
||||
serializationFormat Json
|
||||
template createJsonFlavor(FlavorName: untyped,
|
||||
skipNullFields = false) {.dirty.} =
|
||||
type FlavorName = object
|
||||
|
||||
template Reader(T: type FlavorName): type = Reader(Json, FlavorName)
|
||||
type
|
||||
JsonReader[Flavor = DefaultFlavor] = object
|
||||
|
||||
Json.setReader JsonReader
|
||||
|
||||
template parseArray(r: var JsonReader; body: untyped) =
|
||||
parseArrayImpl(idx): body
|
||||
|
||||
template parseArray(r: var JsonReader; idx: untyped; body: untyped) =
|
||||
parseArrayImpl(idx): body
|
||||
|
||||
proc readRecordValue[T](r: var JsonReader, value: var T) =
|
||||
type
|
||||
ReaderType {.used.} = type r
|
||||
T = type value
|
||||
|
||||
discard T.fieldReadersTable(ReaderType)
|
||||
|
||||
proc readValue[T](r: var JsonReader, value: var T) =
|
||||
mixin readValue
|
||||
|
||||
when value is seq:
|
||||
r.parseArray:
|
||||
readValue(r, value[0])
|
||||
|
||||
elif value is object:
|
||||
readRecordValue(r, value)
|
||||
|
||||
type
|
||||
RemoteSignerInfo = object
|
||||
id: uint32
|
||||
RemoteKeystore = object
|
||||
|
||||
proc readValue(reader: var JsonReader, value: var RemoteKeystore) =
|
||||
discard reader.readValue(seq[RemoteSignerInfo])
|
||||
|
||||
createJsonFlavor RestJson
|
||||
useDefaultReaderIn(RemoteSignerInfo, RestJson)
|
||||
proc readValue(reader: var JsonReader[RestJson], value: var uint64) =
|
||||
discard reader.readValue(string)
|
||||
|
||||
discard Json.decode("", RemoteKeystore)
|
||||
block: # https://github.com/nim-lang/Nim/issues/22874
|
||||
var reader: Reader(RestJson)
|
||||
discard reader.readValue(RemoteSignerInfo)
|
||||
Reference in New Issue
Block a user