implement generic default values for object fields (#24384)

fixes #21941, fixes #23594
This commit is contained in:
metagn
2024-10-30 10:58:04 +03:00
committed by GitHub
parent d61897459d
commit 4091576ab7
5 changed files with 65 additions and 23 deletions

View File

@@ -733,6 +733,7 @@ proc preparePContext*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PCo
result.semInferredLambda = semInferredLambda
result.semGenerateInstance = generateInstance
result.instantiateOnlyProcType = instantiateOnlyProcType
result.fitDefaultNode = fitDefaultNode
result.semTypeNode = semTypeNode
result.instTypeBoundOp = sigmatch.instTypeBoundOp
result.hasUnresolvedArgs = hasUnresolvedArgs

View File

@@ -142,6 +142,7 @@ type
instantiateOnlyProcType*: proc (c: PContext, pt: LayeredIdTable,
prc: PSym, info: TLineInfo): PType
# used by sigmatch for explicit generic instantiations
fitDefaultNode*: proc (c: PContext, n: var PNode, expectedType: PType)
includedFiles*: IntSet # used to detect recursive include files
pureEnumFields*: TStrTable # pure enum fields that can be used unambiguously
userPragmas*: TStrTable

View File

@@ -273,28 +273,23 @@ proc annotateClosureConv(n: PNode) =
for i in 0..<n.len:
annotateClosureConv(n[i])
proc fitDefaultNode(c: PContext, n: PNode): PType =
proc fitDefaultNode(c: PContext, n: var PNode, expectedType: PType) =
inc c.inStaticContext
let expectedType = if n[^2].kind != nkEmpty: semTypeNode(c, n[^2], nil) else: nil
n[^1] = semConstExpr(c, n[^1], expectedType = expectedType)
let oldType = n[^1].typ
n[^1].flags.incl nfSem
if n[^2].kind != nkEmpty:
if expectedType != nil and oldType != expectedType:
n[^1] = fitNodeConsiderViewType(c, expectedType, n[^1], n[^1].info)
changeType(c, n[^1], expectedType, true) # infer types for default fields value
# bug #22926; be cautious that it uses `semConstExpr` to
# evaulate the default fields; it's only natural to use
# `changeType` to infer types for constant values
# that's also the reason why we don't use `semExpr` to check
# the type since two overlapping error messages might be produced
annotateClosureConv(n)
result = n[^1].typ
else:
result = n[^1].typ
n = semConstExpr(c, n, expectedType = expectedType)
let oldType = n.typ
n.flags.incl nfSem
if expectedType != nil and oldType != expectedType:
n = fitNodeConsiderViewType(c, expectedType, n, n.info)
changeType(c, n, expectedType, true) # infer types for default fields value
# bug #22926; be cautious that it uses `semConstExpr` to
# evaulate the default fields; it's only natural to use
# `changeType` to infer types for constant values
# that's also the reason why we don't use `semExpr` to check
# the type since two overlapping error messages might be produced
annotateClosureConv(n)
# xxx any troubles related to defaults fields, consult `semConst` for a potential answer
if n[^1].kind != nkNilLit:
typeAllowedCheck(c, n.info, result, skConst, {taProcContextIsNotMacro, taIsDefaultField})
if n.kind != nkNilLit:
typeAllowedCheck(c, n.info, n.typ, skConst, {taProcContextIsNotMacro, taIsDefaultField})
dec c.inStaticContext
proc isRecursiveType*(t: PType): bool =
@@ -521,7 +516,14 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
checkMinSonsLen(a, 3, c.config)
var hasDefaultField = a[^1].kind != nkEmpty
if hasDefaultField:
typ = fitDefaultNode(c, a)
typ = if a[^2].kind != nkEmpty: semTypeNode(c, a[^2], nil) else: nil
if c.inGenericContext > 0:
a[^1] = semExprWithType(c, a[^1], {efDetermineType, efAllowSymChoice}, typ)
if typ == nil:
typ = a[^1].typ
else:
fitDefaultNode(c, a[^1], typ)
typ = a[^1].typ
elif a[^2].kind != nkEmpty:
typ = semTypeNode(c, a[^2], nil)
if c.graph.config.isDefined("nimPreviewRangeDefault") and typ.skipTypes(abstractInst).kind == tyRange:
@@ -885,8 +887,15 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
var typ: PType
var hasDefaultField = n[^1].kind != nkEmpty
if hasDefaultField:
typ = fitDefaultNode(c, n)
propagateToOwner(rectype, typ)
typ = if n[^2].kind != nkEmpty: semTypeNode(c, n[^2], nil) else: nil
if c.inGenericContext > 0:
n[^1] = semExprWithType(c, n[^1], {efDetermineType, efAllowSymChoice}, typ)
if typ == nil:
typ = n[^1].typ
else:
fitDefaultNode(c, n[^1], typ)
typ = n[^1].typ
propagateToOwner(rectype, typ)
elif n[^2].kind == nkEmpty:
localError(c.config, n.info, errTypeExpected)
typ = errorType(c)

View File

@@ -276,6 +276,11 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0; expectedType: PT
replaceTypeVarsS(cl, n.sym, result.typ)
else:
replaceTypeVarsS(cl, n.sym, replaceTypeVarsT(cl, n.sym.typ))
if result.sym.kind == skField and result.sym.ast != nil and
(cl.owner == nil or result.sym.owner == cl.owner):
# instantiate default value of object/tuple field
cl.c.fitDefaultNode(cl.c, result.sym.ast, result.sym.typ)
result.sym.typ = result.sym.ast.typ
# sym type can be nil if was gensym created by macro, see #24048
if result.sym.typ != nil and result.sym.typ.kind == tyVoid:
# don't add the 'void' field

View File

@@ -0,0 +1,26 @@
block: # issue #23594
type
Gen[T] = object
a: T = 1.0
Spec32 = Gen[float32]
Spec64 = Gen[float64]
var
a: Spec32
b: Spec64
doAssert sizeof(a) == 4
doAssert sizeof(b) == 8
doAssert a.a is float32
doAssert b.a is float64
block: # issue #21941
func what[T](): T =
123
type MyObject[T] = object
f: T = what[T]()
var m: MyObject[float] = MyObject[float]()
doAssert m.f is float
doAssert m.f == 123.0