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
commit 7652aede41
4 changed files with 61 additions and 15 deletions

View file

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

View file

@ -65,7 +65,7 @@ const
wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked, wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
wBorrow, wGcSafe, wPartial, wExplain, wPackage} wBorrow, wGcSafe, wPartial, wExplain, wPackage}
fieldPragmas* = declPragmas + { fieldPragmas* = declPragmas + {
wGuard, wBitsize, wCursor} - {wExportNims, wNodecl} # why exclude these? wGuard, wBitsize, wCursor, wRequiresInit} - {wExportNims, wNodecl} # why exclude these?
varPragmas* = declPragmas + {wVolatile, wRegister, wThreadVar, varPragmas* = declPragmas + {wVolatile, wRegister, wThreadVar,
wMagic, wHeader, wCompilerProc, wCore, wDynlib, wMagic, wHeader, wCompilerProc, wCore, wDynlib,
wNoInit, wCompileTime, wGlobal, wNoInit, wCompileTime, wGlobal,
@ -1087,8 +1087,12 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
else: incl(sym.typ.flags, tfUnion) else: incl(sym.typ.flags, tfUnion)
of wRequiresInit: of wRequiresInit:
noVal(c, it) noVal(c, it)
if sym.typ == nil: invalidPragma(c, it) if sym.kind == skField:
else: incl(sym.typ.flags, tfRequiresInit) sym.flags.incl sfRequiresInit
elif sym.typ != nil:
incl(sym.typ.flags, tfRequiresInit)
else:
invalidPragma(c, it)
of wByRef: of wByRef:
noVal(c, it) noVal(c, it)
if sym == nil or sym.typ == nil: 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 ", " if result.len != 0: result.add ", "
result.add field.sym.name.s.quoteStr 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): 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) let assignment = locateFieldInInitExpr(c, r.sym, initExpr)
if assignment == nil: if assignment == nil:
if result.len == 0: if result.len == 0:
@ -147,19 +149,22 @@ proc missingMandatoryFields(c: PContext, fieldsRecList, initExpr: PNode): string
result.add ", " result.add ", "
result.add r.sym.name.s result.add r.sym.name.s
proc checkForMissingFields(c: PContext, recList, initExpr: PNode) = proc checkForMissingFields(c: PContext, recList, initExpr: PNode,
let missing = missingMandatoryFields(c, recList, initExpr) requiresFullInit = false) =
let missing = missingMandatoryFields(c, recList, initExpr, requiresFullInit)
if missing.len > 0: if missing.len > 0:
localError(c.config, initExpr.info, "fields not initialized: $1.", [missing]) localError(c.config, initExpr.info, "fields not initialized: $1.", [missing])
proc semConstructFields(c: PContext, recNode: PNode, proc semConstructFields(c: PContext, recNode: PNode,
initExpr: PNode, flags: TExprFlags): InitStatus = initExpr: PNode, flags: TExprFlags,
requiresFullInit = false): InitStatus =
result = initUnknown result = initUnknown
case recNode.kind case recNode.kind
of nkRecList: of nkRecList:
for field in recNode: for field in recNode:
let status = semConstructFields(c, field, initExpr, flags) let status = semConstructFields(c, field, initExpr,
flags, requiresFullInit)
mergeInitStatus(result, status) mergeInitStatus(result, status)
of nkRecCase: of nkRecCase:
@ -171,7 +176,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
template checkMissingFields(branchNode: PNode) = template checkMissingFields(branchNode: PNode) =
if branchNode != nil: if branchNode != nil:
let fields = branchNode[^1] let fields = branchNode[^1]
checkForMissingFields(c, fields, initExpr) checkForMissingFields(c, fields, initExpr, requiresFullInit)
let discriminator = recNode[0] let discriminator = recNode[0]
internalAssert c.config, discriminator.kind == nkSym internalAssert c.config, discriminator.kind == nkSym
@ -179,7 +184,8 @@ proc semConstructFields(c: PContext, recNode: PNode,
for i in 1..<recNode.len: for i in 1..<recNode.len:
let innerRecords = recNode[i][^1] 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}: if status notin {initNone, initUnknown}:
mergeInitStatus(result, status) mergeInitStatus(result, status)
if selectedBranch != -1: if selectedBranch != -1:
@ -319,16 +325,20 @@ proc semConstructFields(c: PContext, recNode: PNode,
proc semConstructType(c: PContext, initExpr: PNode, proc semConstructType(c: PContext, initExpr: PNode,
t: PType, flags: TExprFlags): InitStatus = t: PType, flags: TExprFlags): InitStatus =
var t = t
result = initUnknown result = initUnknown
var
t = t
requiresFullInit = tfRequiresInit in t.flags
while true: while true:
let status = semConstructFields(c, t.n, initExpr, flags) let status = semConstructFields(c, t.n, initExpr,
flags, requiresFullInit)
mergeInitStatus(result, status) mergeInitStatus(result, status)
if status in {initPartial, initNone, initUnknown}: if status in {initPartial, initNone, initUnknown}:
checkForMissingFields c, t.n, initExpr checkForMissingFields c, t.n, initExpr, requiresFullInit
let base = t[0] let base = t[0]
if base == nil: break if base == nil: break
t = skipTypes(base, skipPtrs) t = skipTypes(base, skipPtrs)
requiresFullInit = requiresFullInit or tfRequiresInit in t.flags
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
var t = semTypeNode(c, n[0], nil) var t = semTypeNode(c, n[0], nil)

View file

@ -3,7 +3,9 @@ template accept(x) =
template reject(x) = template reject(x) =
static: assert(not compiles(x)) static: assert(not compiles(x))
{.experimental: "notnil".} {.experimental: "notnil".}
type type
TRefObj = ref object TRefObj = ref object
x: int x: int
@ -26,6 +28,18 @@ type
else: else:
discard 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 TObj = object
case choice: TChoice case choice: TChoice
of A: 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()) 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 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 # this will be accepted, because the false outer branch will be taken and the inner A branch
accept TNestedChoices() accept TNestedChoices()