More sophistication; Allow requiresInit to be specified per-field

This commit is contained in:
Zahary Karadjov
2020-03-27 18:43:23 +02:00
committed by Andreas Rumpf
parent a8b6222c86
commit 7652aede41
4 changed files with 61 additions and 15 deletions

View File

@@ -259,6 +259,7 @@ type
# needed for the code generator
sfProcvar, # proc can be passed to a proc var
sfDiscriminant, # field is a discriminant in a record/object
sfRequiresInit, # field must be initialized during construction
sfDeprecated, # symbol is deprecated
sfExplain, # provide more diagnostics when this symbol is used
sfError, # usage of symbol should trigger a compile-time error
@@ -1488,7 +1489,7 @@ proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) =
elif owner.kind notin HaveTheirOwnEmpty:
owner.flags.incl tfHasRequiresInit
if tfRequiresInit in elem.flags:
if {tfRequiresInit, tfHasRequiresInit} * elem.flags != {}:
if owner.kind in HaveTheirOwnEmpty: discard
else: owner.flags.incl tfHasRequiresInit

View File

@@ -65,7 +65,7 @@ const
wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
wBorrow, wGcSafe, wPartial, wExplain, wPackage}
fieldPragmas* = declPragmas + {
wGuard, wBitsize, wCursor} - {wExportNims, wNodecl} # why exclude these?
wGuard, wBitsize, wCursor, wRequiresInit} - {wExportNims, wNodecl} # why exclude these?
varPragmas* = declPragmas + {wVolatile, wRegister, wThreadVar,
wMagic, wHeader, wCompilerProc, wCore, wDynlib,
wNoInit, wCompileTime, wGlobal,
@@ -1087,8 +1087,12 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
else: incl(sym.typ.flags, tfUnion)
of wRequiresInit:
noVal(c, it)
if sym.typ == nil: invalidPragma(c, it)
else: incl(sym.typ.flags, tfRequiresInit)
if sym.kind == skField:
sym.flags.incl sfRequiresInit
elif sym.typ != nil:
incl(sym.typ.flags, tfRequiresInit)
else:
invalidPragma(c, it)
of wByRef:
noVal(c, it)
if sym == nil or sym.typ == nil:

View File

@@ -136,9 +136,11 @@ proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): strin
if result.len != 0: result.add ", "
result.add field.sym.name.s.quoteStr
proc missingMandatoryFields(c: PContext, fieldsRecList, initExpr: PNode): string =
proc missingMandatoryFields(c: PContext, fieldsRecList, initExpr: PNode,
requiresFullInit = false): string =
for r in directFieldsInRecList(fieldsRecList):
if {tfNotNil, tfRequiresInit} * r.sym.typ.flags != {}:
if requiresFullInit or sfRequiresInit in r.sym.flags or
{tfNotNil, tfRequiresInit, tfHasRequiresInit} * r.sym.typ.flags != {}:
let assignment = locateFieldInInitExpr(c, r.sym, initExpr)
if assignment == nil:
if result.len == 0:
@@ -147,19 +149,22 @@ proc missingMandatoryFields(c: PContext, fieldsRecList, initExpr: PNode): string
result.add ", "
result.add r.sym.name.s
proc checkForMissingFields(c: PContext, recList, initExpr: PNode) =
let missing = missingMandatoryFields(c, recList, initExpr)
proc checkForMissingFields(c: PContext, recList, initExpr: PNode,
requiresFullInit = false) =
let missing = missingMandatoryFields(c, recList, initExpr, requiresFullInit)
if missing.len > 0:
localError(c.config, initExpr.info, "fields not initialized: $1.", [missing])
proc semConstructFields(c: PContext, recNode: PNode,
initExpr: PNode, flags: TExprFlags): InitStatus =
initExpr: PNode, flags: TExprFlags,
requiresFullInit = false): InitStatus =
result = initUnknown
case recNode.kind
of nkRecList:
for field in recNode:
let status = semConstructFields(c, field, initExpr, flags)
let status = semConstructFields(c, field, initExpr,
flags, requiresFullInit)
mergeInitStatus(result, status)
of nkRecCase:
@@ -171,7 +176,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
template checkMissingFields(branchNode: PNode) =
if branchNode != nil:
let fields = branchNode[^1]
checkForMissingFields(c, fields, initExpr)
checkForMissingFields(c, fields, initExpr, requiresFullInit)
let discriminator = recNode[0]
internalAssert c.config, discriminator.kind == nkSym
@@ -179,7 +184,8 @@ proc semConstructFields(c: PContext, recNode: PNode,
for i in 1..<recNode.len:
let innerRecords = recNode[i][^1]
let status = semConstructFields(c, innerRecords, initExpr, flags)
let status = semConstructFields(c, innerRecords, initExpr,
flags, requiresFullInit)
if status notin {initNone, initUnknown}:
mergeInitStatus(result, status)
if selectedBranch != -1:
@@ -319,16 +325,20 @@ proc semConstructFields(c: PContext, recNode: PNode,
proc semConstructType(c: PContext, initExpr: PNode,
t: PType, flags: TExprFlags): InitStatus =
var t = t
result = initUnknown
var
t = t
requiresFullInit = tfRequiresInit in t.flags
while true:
let status = semConstructFields(c, t.n, initExpr, flags)
let status = semConstructFields(c, t.n, initExpr,
flags, requiresFullInit)
mergeInitStatus(result, status)
if status in {initPartial, initNone, initUnknown}:
checkForMissingFields c, t.n, initExpr
checkForMissingFields c, t.n, initExpr, requiresFullInit
let base = t[0]
if base == nil: break
t = skipTypes(base, skipPtrs)
requiresFullInit = requiresFullInit or tfRequiresInit in t.flags
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
var t = semTypeNode(c, n[0], nil)

View File

@@ -3,7 +3,9 @@ template accept(x) =
template reject(x) =
static: assert(not compiles(x))
{.experimental: "notnil".}
type
TRefObj = ref object
x: int
@@ -26,6 +28,18 @@ type
else:
discard
PartialRequiresInit = object
a {.requiresInit.}: int
b: string
FullRequiresInit {.requiresInit.} = object
a: int
b: int
FullRequiresInitWithParent {.requiresInit.} = object of THasNotNils
e: int
d: int
TObj = object
case choice: TChoice
of A:
@@ -106,6 +120,23 @@ accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: m
reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: THasNotNilsRef())
accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: THasNotNilsRef(a: notNilRef, b: notNilRef))
# Accept only instances where the `a` field is present
accept PartialRequiresInit(a: 10, b: "x")
accept PartialRequiresInit(a: 20)
reject PartialRequiresInit(b: "x")
reject PartialRequiresInit()
accept FullRequiresInit(a: 10, b: 20)
reject FullRequiresInit(a: 10)
reject FullRequiresInit(b: 20)
accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: notNilRef, e: 10, d: 20)
accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10, d: 20)
reject FullRequiresInitWithParent(a: notNilRef, b: nil, c: nil, e: 10, d: 20) # b should not be nil
reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, e: 10, d: 20) # c should not be missing
reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10) # d should not be missing
reject FullRequiresInitWithParent()
# this will be accepted, because the false outer branch will be taken and the inner A branch
accept TNestedChoices()