Skip to content

Commit d0b86dd

Browse files
committed
Replace tfHasRequiresInit with a more accurate mechanism
The new mechanism can deal with more complex scenarios such as not nil field appearing in a non-default case object branch or a field within a generic object that may depend on a when branch. The commit also plugs another hole: the user is no longer able to create illegal default values through seq.setLen(N).
1 parent 98ccf47 commit d0b86dd

11 files changed

+181
-45
lines changed

compiler/ast.nim

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -517,11 +517,11 @@ type
517517
tfIterator, # type is really an iterator, not a tyProc
518518
tfPartial, # type is declared as 'partial'
519519
tfNotNil, # type cannot be 'nil'
520-
521-
tfHasRequiresInit,# type constains a "not nil" constraint somewhere or
520+
tfRequiresInit, # type constains a "not nil" constraint somewhere or
522521
# a `requiresInit` field, so the default zero init
523522
# is not appropriate
524-
tfRequiresInit, # all fields of the type must be initialized
523+
tfNeedsFullInit, # object type marked with {.requiresInit.}
524+
# all fields must be initialized
525525
tfVarIsPtr, # 'var' type is translated like 'ptr' even in C++ mode
526526
tfHasMeta, # type contains "wildcard" sub-types such as generic params
527527
# or other type classes
@@ -1397,7 +1397,7 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType =
13971397
proc exactReplica*(t: PType): PType = copyType(t, t.owner, true)
13981398

13991399
template requiresInit*(t: PType): bool =
1400-
t.flags * {tfRequiresInit, tfHasRequiresInit, tfNotNil} != {}
1400+
t.flags * {tfRequiresInit, tfNotNil} != {}
14011401

14021402
proc copySym*(s: PSym): PSym =
14031403
result = newSym(s.kind, s.name, s.owner, s.info, s.options)
@@ -1489,12 +1489,6 @@ proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) =
14891489
if tfNotNil in elem.flags:
14901490
if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvocation}:
14911491
owner.flags.incl tfNotNil
1492-
elif owner.kind notin HaveTheirOwnEmpty:
1493-
owner.flags.incl tfHasRequiresInit
1494-
1495-
if {tfRequiresInit, tfHasRequiresInit} * elem.flags != {}:
1496-
if owner.kind in HaveTheirOwnEmpty: discard
1497-
else: owner.flags.incl tfHasRequiresInit
14981492

14991493
if elem.isMetaType:
15001494
owner.flags.incl tfHasMeta

compiler/pragmas.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
10901090
if sym.kind == skField:
10911091
sym.flags.incl sfRequiresInit
10921092
elif sym.typ != nil:
1093-
incl(sym.typ.flags, tfRequiresInit)
1093+
incl(sym.typ.flags, tfNeedsFullInit)
10941094
else:
10951095
invalidPragma(c, it)
10961096
of wByRef:

compiler/sem.nim

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@ proc addParams(c: PContext, n: PNode, kind: TSymKind)
4646
proc maybeAddResult(c: PContext, s: PSym, n: PNode)
4747
proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode
4848
proc activate(c: PContext, n: PNode)
49-
proc checkDefaultConstruction(c: PContext, typ: PType, info: TLineInfo)
5049
proc semQuoteAst(c: PContext, n: PNode): PNode
5150
proc finishMethod(c: PContext, s: PSym)
5251
proc evalAtCompileTime(c: PContext, n: PNode): PNode
5352
proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode
5453
proc semStaticExpr(c: PContext, n: PNode): PNode
5554
proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType
5655
proc semTypeOf(c: PContext; n: PNode): PNode
56+
proc computeRequiresInit(c: PContext, t: PType): bool
57+
proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo)
5758
proc hasUnresolvedArgs(c: PContext, n: PNode): bool
5859
proc isArrayConstr(n: PNode): bool {.inline.} =
5960
result = n.kind == nkBracket and
@@ -512,6 +513,7 @@ proc myOpen(graph: ModuleGraph; module: PSym): PPassContext =
512513
c.semExpr = semExpr
513514
c.semTryExpr = tryExpr
514515
c.semTryConstExpr = tryConstExpr
516+
c.computeRequiresInit = computeRequiresInit
515517
c.semOperand = semOperand
516518
c.semConstBoolExpr = semConstBoolExpr
517519
c.semOverloadedCall = semOverloadedCall

compiler/semdata.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ type
103103
semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
104104
semTryExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
105105
semTryConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.}
106+
computeRequiresInit*: proc (c: PContext, t: PType): bool {.nimcall.}
106107
semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
107108
semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet
108109
semOverloadedCall*: proc (c: PContext, n, nOrig: PNode,

compiler/semexprs.nim

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2267,13 +2267,19 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
22672267
of mSizeOf:
22682268
markUsed(c, n.info, s)
22692269
result = semSizeof(c, setMs(n, s))
2270+
of mSetLengthSeq:
2271+
result = semDirectOp(c, n, flags)
2272+
let seqType = result[1].typ.skipTypes({tyPtr, tyRef, # in case we had auto-dereferencing
2273+
tyVar, tyGenericInst, tyOwned, tySink,
2274+
tyAlias, tyUserTypeClassInst})
2275+
if seqType.kind == tySequence and seqType.base.requiresInit:
2276+
localError(c.config, n.info, "setLen can potentially expand the sequence, " &
2277+
"but the element type $1 doesn't have a default value.",
2278+
[typeToString(seqType.base)])
22702279
of mDefault:
22712280
result = semDirectOp(c, n, flags)
22722281
c.config.internalAssert result[1].typ.kind == tyTypeDesc
2273-
let typ = result[1].typ.base
2274-
if typ.kind == tyObject:
2275-
checkDefaultConstruction(c, typ, n.info)
2276-
elif typ.kind in {tyRef, tyPtr} and tfNotNil in typ.flags:
2282+
if result[1].typ.base.requiresInit:
22772283
localError(c.config, n.info, "not nil types don't have a default value")
22782284
else:
22792285
result = semDirectOp(c, n, flags)

compiler/semobjconstr.nim

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type
1515
ObjConstrContext = object
1616
typ: PType # The constructed type
1717
initExpr: PNode # The init expression (nkObjConstr)
18-
requiresFullInit: bool # A `requiresInit` derived type will
18+
needsFullInit: bool # A `requiresInit` derived type will
1919
# set this to true while visiting
2020
# parent types.
2121
missingFields: seq[PSym] # Fields that the user failed to specify
@@ -147,7 +147,7 @@ proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): strin
147147
proc collectMissingFields(c: PContext, fieldsRecList: PNode,
148148
constrCtx: var ObjConstrContext) =
149149
for r in directFieldsInRecList(fieldsRecList):
150-
if constrCtx.requiresFullInit or
150+
if constrCtx.needsFullInit or
151151
sfRequiresInit in r.sym.flags or
152152
r.sym.typ.requiresInit:
153153
let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr)
@@ -323,14 +323,11 @@ proc semConstructFields(c: PContext, recNode: PNode,
323323
else:
324324
internalAssert c.config, false
325325

326-
proc semConstructType(c: PContext, initExpr: PNode,
327-
t: PType, flags: TExprFlags): InitStatus =
326+
proc semConstructTypeAux(c: PContext,
327+
constrCtx: var ObjConstrContext,
328+
flags: TExprFlags): InitStatus =
328329
result = initUnknown
329-
330-
var
331-
t = t
332-
constrCtx = ObjConstrContext(typ: t, initExpr: initExpr,
333-
requiresFullInit: tfRequiresInit in t.flags)
330+
var t = constrCtx.typ
334331
while true:
335332
let status = semConstructFields(c, t.n, constrCtx, flags)
336333
mergeInitStatus(result, status)
@@ -339,18 +336,30 @@ proc semConstructType(c: PContext, initExpr: PNode,
339336
let base = t[0]
340337
if base == nil: break
341338
t = skipTypes(base, skipPtrs)
342-
constrCtx.requiresFullInit = constrCtx.requiresFullInit or
343-
tfRequiresInit in t.flags
344-
345-
# It's possible that the object was not fully initialized while
346-
# specifying a .requiresInit. pragma:
347-
if constrCtx.missingFields.len > 0:
348-
localError(c.config, constrCtx.initExpr.info,
349-
"The $1 type requires the following fields to be initialized: $2.",
350-
[constrCtx.typ.sym.name.s, listSymbolNames(constrCtx.missingFields)])
351-
352-
proc checkDefaultConstruction(c: PContext, typ: PType, info: TLineInfo) =
353-
discard semConstructType(c, newNodeI(nkObjConstr, info), typ, {})
339+
constrCtx.needsFullInit = constrCtx.needsFullInit or
340+
tfNeedsFullInit in t.flags
341+
342+
proc initConstrContext(t: PType, initExpr: PNode): ObjConstrContext =
343+
ObjConstrContext(typ: t, initExpr: initExpr,
344+
needsFullInit: tfNeedsFullInit in t.flags)
345+
346+
proc computeRequiresInit(c: PContext, t: PType): bool =
347+
assert t.kind == tyObject
348+
var constrCtx = initConstrContext(t, newNode(nkObjConstr))
349+
let initResult = semConstructTypeAux(c, constrCtx, {})
350+
constrCtx.missingFields.len > 0
351+
352+
proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) =
353+
var objType = t
354+
while objType.kind != tyObject:
355+
objType = objType.lastSon
356+
assert objType != nil
357+
var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info))
358+
let initResult = semConstructTypeAux(c, constrCtx, {})
359+
assert constrCtx.missingFields.len > 0
360+
localError(c.config, info,
361+
"The $1 type doesn't have a default value. The following fields must be initialized: $2.",
362+
[typeToString(t), listSymbolNames(constrCtx.missingFields)])
354363

355364
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
356365
var t = semTypeNode(c, n[0], nil)
@@ -376,7 +385,15 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
376385
# Check if the object is fully initialized by recursively testing each
377386
# field (if this is a case object, initialized fields in two different
378387
# branches will be reported as an error):
379-
let initResult = semConstructType(c, result, t, flags)
388+
var constrCtx = initConstrContext(t, result)
389+
let initResult = semConstructTypeAux(c, constrCtx, flags)
390+
391+
# It's possible that the object was not fully initialized while
392+
# specifying a .requiresInit. pragma:
393+
if constrCtx.missingFields.len > 0:
394+
localError(c.config, result.info,
395+
"The $1 type requires the following fields to be initialized: $2.",
396+
[t.sym.name.s, listSymbolNames(constrCtx.missingFields)])
380397

381398
# Since we were traversing the object fields, it's possible that
382399
# not all of the fields specified in the constructor was visited.

compiler/sempass2.nim

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ proc initVar(a: PEffects, n: PNode; volatileCheck: bool) =
184184
proc initVarViaNew(a: PEffects, n: PNode) =
185185
if n.kind != nkSym: return
186186
let s = n.sym
187-
if {tfRequiresInit, tfHasRequiresInit, tfNotNil} * s.typ.flags <= {tfNotNil}:
187+
if {tfRequiresInit, tfNotNil} * s.typ.flags <= {tfNotNil}:
188188
# 'x' is not nil, but that doesn't mean its "not nil" children
189189
# are initialized:
190190
initVar(a, n, volatileCheck=true)
@@ -590,7 +590,7 @@ proc trackCase(tracked: PEffects, n: PNode) =
590590
track(tracked, n[0])
591591
let oldState = tracked.init.len
592592
let oldFacts = tracked.guards.s.len
593-
let stringCase = skipTypes(n[0].typ,
593+
let stringCase = n[0].typ != nil and skipTypes(n[0].typ,
594594
abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString}
595595
let interesting = not stringCase and interestingCaseExpr(n[0]) and
596596
tracked.config.hasWarn(warnProveField)
@@ -838,7 +838,7 @@ proc track(tracked: PEffects, n: PNode) =
838838
# may not look like an assignment, but it is:
839839
let arg = n[1]
840840
initVarViaNew(tracked, arg)
841-
if arg.typ.len != 0 and {tfRequiresInit, tfHasRequiresInit} * arg.typ.lastSon.flags != {}:
841+
if arg.typ.len != 0 and {tfRequiresInit} * arg.typ.lastSon.flags != {}:
842842
if a.sym.magic == mNewSeq and n[2].kind in {nkCharLit..nkUInt64Lit} and
843843
n[2].intVal == 0:
844844
# var s: seq[notnil]; newSeq(s, 0) is a special case!

compiler/semstmts.nim

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,8 +614,10 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
614614
else: v.typ = tup
615615
b[j] = newSymNode(v)
616616
if def.kind == nkEmpty:
617-
if v.typ.kind == tyObject:
618-
checkDefaultConstruction(c, v.typ, v.info)
617+
let actualType = v.typ.skipTypes({tyGenericInst, tyAlias,
618+
tyUserTypeClassInst})
619+
if actualType.kind == tyObject and actualType.requiresInit:
620+
defaultConstructionError(c, v.typ, v.info)
619621
else:
620622
checkNilable(c, v)
621623
if sfCompileTime in v.flags:

compiler/semtypes.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -770,8 +770,6 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
770770
f.typ = typ
771771
f.position = pos
772772
f.options = c.config.options
773-
if sfRequiresInit in f.flags:
774-
rectype.flags.incl tfHasRequiresInit
775773
if fieldOwner != nil and
776774
{sfImportc, sfExportc} * fieldOwner.flags != {} and
777775
not hasCaseFields and f.loc.r == nil:
@@ -879,6 +877,8 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; isInheritable: bool): PTy
879877
pragma(c, s, n[0], typePragmas)
880878
if base == nil and tfInheritable notin result.flags:
881879
incl(result.flags, tfFinal)
880+
if c.inGenericContext == 0 and computeRequiresInit(c, result):
881+
result.flags.incl tfRequiresInit
882882

883883
proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
884884
if n.len < 1:

compiler/semtypinst.nim

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,8 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
613613

614614
of tyObject, tyTuple:
615615
propagateFieldFlags(result, result.n)
616+
if result.kind == tyObject and cl.c.computeRequiresInit(cl.c, result):
617+
result.flags.incl tfRequiresInit
616618

617619
of tyProc:
618620
eraseVoidParams(result)

tests/constructors/tinvalid_construction.nim

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ type
1010
TRefObj = ref object
1111
x: int
1212

13+
IllegalToConstruct = object
14+
x: cstring not nil
15+
1316
THasNotNils = object of RootObj
1417
a: TRefObj not nil
1518
b: TRefObj not nil
@@ -89,6 +92,10 @@ proc userDefinedDefault(T: typedesc): T =
8992
proc genericDefault(T: typedesc): T =
9093
result = default(T)
9194

95+
reject IllegalToConstruct()
96+
reject:
97+
var x: IllegalToConstruct
98+
9299
accept TObj()
93100
accept TObj(choice: A)
94101
reject TObj(choice: A, bc: 10) # bc is in the wrong branch
@@ -256,6 +263,111 @@ reject TNestedChoices(outerChoice: false, innerChoice: C)
256263
accept TNestedChoices(outerChoice: false, innerChoice: C, notnil: notNilRef)
257264
reject TNestedChoices(outerChoice: false, innerChoice: C, notnil: nil)
258265

266+
# Tests involving generics and sequences:
267+
#
268+
block:
269+
# This test aims to show that it's possible to instantiate and
270+
# use a sequence with a requiresInit type:
271+
272+
var legalSeq: seq[IllegalToConstruct]
273+
legalSeq.add IllegalToConstruct(x: "one")
274+
var two = IllegalToConstruct(x: "two")
275+
legalSeq.add two
276+
var one = legalSeq[0]
277+
var twoAgain = legalSeq.pop
278+
279+
# It's not possible to tell the sequence to create elements
280+
# for us though:
281+
reject:
282+
var illegalSeq = newSeq[IllegalToConstruct](10)
283+
284+
reject:
285+
var illegalSeq: seq[IllegalToConstruct]
286+
newSeq(illegalSeq, 10)
287+
288+
reject:
289+
var illegalSeq: seq[IllegalToConstruct]
290+
illegalSeq.setLen 10
291+
292+
# You can still use newSeqOfCap to write efficient code:
293+
var anotherLegalSequence = newSeqOfCap[IllegalToConstruct](10)
294+
for i in 0..9:
295+
anotherLegalSequence.add IllegalToConstruct(x: "x")
296+
297+
type DefaultConstructible[yesOrNo: static[bool]] = object
298+
when yesOrNo:
299+
x: string
300+
else:
301+
x: cstring not nil
302+
303+
block:
304+
# Constructability may also depend on the generic parameters of the type:
305+
accept:
306+
var a: DefaultConstructible[true]
307+
var b = DefaultConstructible[true]()
308+
var c = DefaultConstructible[true](x: "test")
309+
var d = DefaultConstructible[false](x: "test")
310+
311+
reject:
312+
var y: DefaultConstructible[false]
313+
314+
reject:
315+
var y = DefaultConstructible[false]()
316+
317+
block:
318+
type
319+
Hash = int
320+
321+
HashTableSlotType = enum
322+
Free = Hash(0)
323+
Deleted = Hash(1)
324+
HasKey = Hash(2)
325+
326+
KeyValuePair[A, B] = object
327+
key: A
328+
case hash: HashTableSlotType
329+
of Free, Deleted:
330+
discard
331+
else:
332+
value: B
333+
334+
# The above KeyValuePair is an interesting type because it
335+
# may become unconstructible depending on the generic parameters:
336+
accept KeyValuePair[int, string](hash: Deleted)
337+
accept KeyValuePair[int, IllegalToConstruct](hash: Deleted)
338+
339+
accept KeyValuePair[int, string](hash: HasKey)
340+
reject KeyValuePair[int, IllegalToConstruct](hash: HasKey)
341+
342+
# Since all the above variations don't have a non-constructible
343+
# field in the default branch of the case object, we can construct
344+
# such values:
345+
accept KeyValuePair[int, string]()
346+
accept KeyValuePair[int, IllegalToConstruct]()
347+
accept KeyValuePair[DefaultConstructible[true], string]()
348+
accept KeyValuePair[DefaultConstructible[true], IllegalToConstruct]()
349+
350+
var a: KeyValuePair[int, string]
351+
var b: KeyValuePair[int, IllegalToConstruct]
352+
var c: KeyValuePair[DefaultConstructible[true], string]
353+
var d: KeyValuePair[DefaultConstructible[true], IllegalToConstruct]
354+
var s1 = newSeq[KeyValuePair[int, IllegalToConstruct]](10)
355+
var s2 = newSeq[KeyValuePair[DefaultConstructible[true], IllegalToConstruct]](10)
356+
357+
# But let's put the non-constructible values as keys:
358+
reject KeyValuePair[IllegalToConstruct, int](hash: Deleted)
359+
reject KeyValuePair[IllegalToConstruct, int]()
360+
361+
type IllegalPair = KeyValuePair[DefaultConstructible[false], string]
362+
363+
reject:
364+
var x: IllegalPair
365+
366+
reject:
367+
var s = newSeq[IllegalPair](10)
368+
369+
# Specific issues:
370+
#
259371
block:
260372
# https://github.com/nim-lang/Nim/issues/11428
261373
type

0 commit comments

Comments
 (0)