diff --git a/compiler/ast.nim b/compiler/ast.nim index 4d45f0e5581ed..f4f68081172ae 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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 @@ -516,9 +517,11 @@ type tfIterator, # type is really an iterator, not a tyProc tfPartial, # type is declared as 'partial' tfNotNil, # type cannot be 'nil' - - tfNeedsInit, # type constains a "not nil" constraint somewhere or some - # other type so that it requires initialization + tfRequiresInit, # type constains a "not nil" constraint somewhere or + # a `requiresInit` field, so the default zero init + # is not appropriate + tfNeedsFullInit, # object type marked with {.requiresInit.} + # all fields must be initialized tfVarIsPtr, # 'var' type is translated like 'ptr' even in C++ mode tfHasMeta, # type contains "wildcard" sub-types such as generic params # or other type classes @@ -1393,6 +1396,9 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType = proc exactReplica*(t: PType): PType = copyType(t, t.owner, true) +template requiresInit*(t: PType): bool = + t.flags * {tfRequiresInit, tfNotNil} != {} + proc copySym*(s: PSym): PSym = result = newSym(s.kind, s.name, s.owner, s.info, s.options) #result.ast = nil # BUGFIX; was: s.ast which made problems @@ -1483,12 +1489,6 @@ proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) = if tfNotNil in elem.flags: if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvocation}: owner.flags.incl tfNotNil - elif owner.kind notin HaveTheirOwnEmpty: - owner.flags.incl tfNeedsInit - - if tfNeedsInit in elem.flags: - if owner.kind in HaveTheirOwnEmpty: discard - else: owner.flags.incl tfNeedsInit if elem.isMetaType: owner.flags.incl tfHasMeta diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index 968918ba9711a..bd60ddd7db27a 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -1041,3 +1041,10 @@ proc isAddrNode*(n: PNode): bool = if n[0].kind == nkSym and n[0].sym.magic == mAddr: true else: false else: false + +proc listSymbolNames*(symbols: openArray[PSym]): string = + for sym in symbols: + if result.len > 0: + result.add ", " + result.add sym.name.s + diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index f7f3b7706aa9b..658863676d786 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -23,6 +23,7 @@ type errGeneralParseError, errNewSectionExpected, errInvalidDirectiveX, + errProveInit, errGenerated, errUser, warnCannotOpenFile, @@ -36,6 +37,8 @@ type warnUnusedImportX, warnInheritFromException, warnEachIdentIsTuple, + warnUnsafeSetLen, + warnUnsafeDefault, warnProveInit, warnProveField, warnProveIndex, warnStaticIndexCheck, warnGcUnsafe, warnGcUnsafe2, warnUninit, warnGcMem, warnDestructor, warnLockLevel, warnResultShadowed, @@ -63,6 +66,7 @@ const errGeneralParseError: "general parse error", errNewSectionExpected: "new section expected", errInvalidDirectiveX: "invalid directive: '$1'", + errProveInit: "Cannot prove that '$1' is initialized.", errGenerated: "$1", errUser: "$1", warnCannotOpenFile: "cannot open '$1'", @@ -85,6 +89,9 @@ const warnUnusedImportX: "imported and not used: '$1'", warnInheritFromException: "inherit from a more precise exception type like ValueError, IOError or OSError", warnEachIdentIsTuple: "each identifier is a tuple", + warnUnsafeSetLen: "setLen can potentially expand the sequence, " & + "but the element type '$1' doesn't have a valid default value", + warnUnsafeDefault: "The '$1' type doesn't have a valid default value", warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future.", warnProveField: "cannot prove that field '$1' is accessible", warnProveIndex: "cannot prove index '$1' is valid", @@ -144,6 +151,7 @@ const "TypelessParam", "UseBase", "WriteToForeignHeap", "UnsafeCode", "UnusedImport", "InheritFromException", "EachIdentIsTuple", + "UnsafeSetLen", "UnsafeDefault", "ProveInit", "ProveField", "ProveIndex", "IndexCheck", "GcUnsafe", "GcUnsafe2", "Uninit", "GcMem", "Destructor", "LockLevel", "ResultShadowed", diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 6056afb6c348d..6816a1705d743 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -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, @@ -637,10 +637,13 @@ proc processPragma(c: PContext, n: PNode, i: int) = proc pragmaRaisesOrTags(c: PContext, n: PNode) = proc processExc(c: PContext, x: PNode) = - var t = skipTypes(c.semTypeNode(c, x, nil), skipPtrs) - if t.kind != tyObject: - localError(c.config, x.info, errGenerated, "invalid type for raises/tags list") - x.typ = t + if c.hasUnresolvedArgs(c, x): + x.typ = makeTypeFromExpr(c, x) + else: + var t = skipTypes(c.semTypeNode(c, x, nil), skipPtrs) + if t.kind != tyObject and not t.isMetaType: + localError(c.config, x.info, errGenerated, "invalid type for raises/tags list") + x.typ = t if n.kind in nkPragmaCallKinds and n.len == 2: let it = n[1] @@ -1087,8 +1090,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, tfNeedsInit) + if sym.kind == skField: + sym.flags.incl sfRequiresInit + elif sym.typ != nil: + incl(sym.typ.flags, tfNeedsFullInit) + else: + invalidPragma(c, it) of wByRef: noVal(c, it) if sym == nil or sym.typ == nil: diff --git a/compiler/sem.nim b/compiler/sem.nim index 48f767af7c569..1ced234f57463 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -53,6 +53,8 @@ proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode proc semStaticExpr(c: PContext, n: PNode): PNode proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType proc semTypeOf(c: PContext; n: PNode): PNode +proc computeRequiresInit(c: PContext, t: PType): bool +proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) proc hasUnresolvedArgs(c: PContext, n: PNode): bool proc isArrayConstr(n: PNode): bool {.inline.} = result = n.kind == nkBracket and @@ -511,6 +513,7 @@ proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = c.semExpr = semExpr c.semTryExpr = tryExpr c.semTryConstExpr = tryConstExpr + c.computeRequiresInit = computeRequiresInit c.semOperand = semOperand c.semConstBoolExpr = semConstBoolExpr c.semOverloadedCall = semOverloadedCall @@ -518,6 +521,7 @@ proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = c.semGenerateInstance = generateInstance c.semTypeNode = semTypeNode c.instTypeBoundOp = sigmatch.instTypeBoundOp + c.hasUnresolvedArgs = hasUnresolvedArgs pushProcCon(c, module) pushOwner(c, c.module) @@ -583,7 +587,7 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode = result = buildEchoStmt(c, result) if c.config.cmd == cmdIdeTools: appendToModule(c.module, result) - trackTopLevelStmt(c, c.module, result) + trackStmt(c, c.module, result, isTopLevel = true) proc recoverContext(c: PContext) = # clean up in case of a semantic error: We clean up the stacks, etc. This is diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 367c0eaf673ae..7ec4c423f9813 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -63,7 +63,7 @@ type # to the user. efWantStmt, efAllowStmt, efDetermineType, efExplain, efAllowDestructor, efWantValue, efOperand, efNoSemCheck, - efNoEvaluateGeneric, efInCall, efFromHlo, + efNoEvaluateGeneric, efInCall, efFromHlo, efNoSem2Check, efNoUndeclared # Use this if undeclared identifiers should not raise an error during # overload resolution. @@ -103,6 +103,9 @@ type semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semTryExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semTryConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} + computeRequiresInit*: proc (c: PContext, t: PType): bool {.nimcall.} + hasUnresolvedArgs*: proc (c: PContext, n: PNode): bool + semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet semOverloadedCall*: proc (c: PContext, n, nOrig: PNode, diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d6659f76b5127..5f82eb1e7d72a 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2082,7 +2082,10 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = var err: string try: result = semExpr(c, n, flags) - if c.config.errorCounter != oldErrorCount: result = nil + if result != nil and efNoSem2Check notin flags: + trackStmt(c, c.module, result, isTopLevel = false) + if c.config.errorCounter != oldErrorCount: + result = nil except ERecoverableError: discard # undo symbol table changes (as far as it's possible): @@ -2644,6 +2647,9 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = return var typ = semTypeNode(c, n, nil).skipTypes({tyTypeDesc}) result.typ = makeTypeDesc(c, typ) + of nkStmtListType: + let typ = semTypeNode(c, n, nil) + result.typ = makeTypeDesc(c, typ) of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: # check if it is an expression macro: checkMinSonsLen(n, 1, c.config) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 8c07d7d183744..7838466501083 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -530,4 +530,19 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode, result = semQuantifier(c, n) of mOld: result = semOld(c, n) - else: result = n + of mSetLengthSeq: + result = n + let seqType = result[1].typ.skipTypes({tyPtr, tyRef, # in case we had auto-dereferencing + tyVar, tyGenericInst, tyOwned, tySink, + tyAlias, tyUserTypeClassInst}) + if seqType.kind == tySequence and seqType.base.requiresInit: + message(c.config, n.info, warnUnsafeSetLen, typeToString(seqType.base)) + of mDefault: + result = n + c.config.internalAssert result[1].typ.kind == tyTypeDesc + let constructed = result[1].typ.base + if constructed.requiresInit: + message(c.config, n.info, warnUnsafeDefault, typeToString(constructed)) + else: + result = n + diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim index 489bc66840c20..682e74440858c 100644 --- a/compiler/semobjconstr.nim +++ b/compiler/semobjconstr.nim @@ -12,7 +12,15 @@ # included from sem.nim type - InitStatus = enum + ObjConstrContext = object + typ: PType # The constructed type + initExpr: PNode # The init expression (nkObjConstr) + needsFullInit: bool # A `requiresInit` derived type will + # set this to true while visiting + # parent types. + missingFields: seq[PSym] # Fields that the user failed to specify + + InitStatus = enum # This indicates the result of object construction initUnknown initFull # All of the fields have been initialized initPartial # Some of the fields have been initialized @@ -136,42 +144,37 @@ 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 collectMissingFields(c: PContext, fieldsRecList: PNode, + constrCtx: var ObjConstrContext) = for r in directFieldsInRecList(fieldsRecList): - if {tfNotNil, tfNeedsInit} * r.sym.typ.flags != {}: - let assignment = locateFieldInInitExpr(c, r.sym, initExpr) + if constrCtx.needsFullInit or + sfRequiresInit in r.sym.flags or + r.sym.typ.requiresInit: + let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr) if assignment == nil: - if result.len == 0: - result = r.sym.name.s - else: - result.add ", " - result.add r.sym.name.s - -proc checkForMissingFields(c: PContext, recList, initExpr: PNode) = - let missing = missingMandatoryFields(c, recList, initExpr) - if missing.len > 0: - localError(c.config, initExpr.info, "fields not initialized: $1.", [missing]) + constrCtx.missingFields.add r.sym proc semConstructFields(c: PContext, recNode: PNode, - initExpr: PNode, flags: TExprFlags): InitStatus = + constrCtx: var ObjConstrContext, + flags: TExprFlags): InitStatus = result = initUnknown case recNode.kind of nkRecList: for field in recNode: - let status = semConstructFields(c, field, initExpr, flags) + let status = semConstructFields(c, field, constrCtx, flags) mergeInitStatus(result, status) of nkRecCase: template fieldsPresentInBranch(branchIdx: int): string = let branch = recNode[branchIdx] let fields = branch[^1] - fieldsPresentInInitExpr(c, fields, initExpr) + fieldsPresentInInitExpr(c, fields, constrCtx.initExpr) - template checkMissingFields(branchNode: PNode) = + template collectMissingFields(branchNode: PNode) = if branchNode != nil: let fields = branchNode[^1] - checkForMissingFields(c, fields, initExpr) + collectMissingFields(c, fields, constrCtx) let discriminator = recNode[0] internalAssert c.config, discriminator.kind == nkSym @@ -179,13 +182,13 @@ proc semConstructFields(c: PContext, recNode: PNode, for i in 1.. 0 + +proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) = + var objType = t + while objType.kind != tyObject: + objType = objType.lastSon + assert objType != nil + var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info)) + let initResult = semConstructTypeAux(c, constrCtx, {}) + assert constrCtx.missingFields.len > 0 + localError(c.config, info, + "The $1 type doesn't have a default value. The following fields must be initialized: $2.", + [typeToString(t), listSymbolNames(constrCtx.missingFields)]) proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = var t = semTypeNode(c, n[0], nil) @@ -354,19 +390,15 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = # Check if the object is fully initialized by recursively testing each # field (if this is a case object, initialized fields in two different # branches will be reported as an error): - let initResult = semConstructType(c, result, t, flags) + var constrCtx = initConstrContext(t, result) + let initResult = semConstructTypeAux(c, constrCtx, flags) # It's possible that the object was not fully initialized while - # specifying a .requiresInit. pragma. - # XXX: Turn this into an error in the next release - if tfNeedsInit in t.flags and initResult != initFull: - # XXX: Disable this warning for now, because tfNeedsInit is propagated - # too aggressively from fields to object types (and this is not correct - # in case objects) - when false: message(n.info, warnUser, - "object type uses the 'requiresInit' pragma, but not all fields " & - "have been initialized. future versions of Nim will treat this as " & - "an error") + # specifying a .requiresInit. pragma: + if constrCtx.missingFields.len > 0: + localError(c.config, result.info, + "The $1 type requires the following fields to be initialized: $2.", + [t.sym.name.s, listSymbolNames(constrCtx.missingFields)]) # Since we were traversing the object fields, it's possible that # not all of the fields specified in the constructor was visited. diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 4658f5454f6a1..86149330e5fa5 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -184,7 +184,7 @@ proc initVar(a: PEffects, n: PNode; volatileCheck: bool) = proc initVarViaNew(a: PEffects, n: PNode) = if n.kind != nkSym: return let s = n.sym - if {tfNeedsInit, tfNotNil} * s.typ.flags <= {tfNotNil}: + if {tfRequiresInit, tfNotNil} * s.typ.flags <= {tfNotNil}: # 'x' is not nil, but that doesn't mean its "not nil" children # are initialized: initVar(a, n, volatileCheck=true) @@ -253,7 +253,7 @@ proc useVar(a: PEffects, n: PNode) = # If the variable is explicitly marked as .noinit. do not emit any error a.init.add s.id elif s.id notin a.init: - if {tfNeedsInit, tfNotNil} * s.typ.flags != {}: + if s.typ.requiresInit: message(a.config, n.info, warnProveInit, s.name.s) else: message(a.config, n.info, warnUninit, s.name.s) @@ -590,7 +590,7 @@ proc trackCase(tracked: PEffects, n: PNode) = track(tracked, n[0]) let oldState = tracked.init.len let oldFacts = tracked.guards.s.len - let stringCase = skipTypes(n[0].typ, + let stringCase = n[0].typ != nil and skipTypes(n[0].typ, abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} let interesting = not stringCase and interestingCaseExpr(n[0]) and tracked.config.hasWarn(warnProveField) @@ -838,7 +838,7 @@ proc track(tracked: PEffects, n: PNode) = # may not look like an assignment, but it is: let arg = n[1] initVarViaNew(tracked, arg) - if arg.typ.len != 0 and {tfNeedsInit} * arg.typ.lastSon.flags != {}: + if arg.typ.len != 0 and {tfRequiresInit} * arg.typ.lastSon.flags != {}: if a.sym.magic == mNewSeq and n[2].kind in {nkCharLit..nkUInt64Lit} and n[2].intVal == 0: # var s: seq[notnil]; newSeq(s, 0) is a special case! @@ -996,6 +996,8 @@ proc track(tracked: PEffects, n: PNode) = createTypeBoundOps(tracked, x[1].typ, n.info) if x.kind == nkExprColonExpr: + if x[0].kind == nkSym: + notNilCheck(tracked, x[1], x[0].sym.typ) checkForSink(tracked.config, tracked.owner, x[1]) else: checkForSink(tracked.config, tracked.owner, x) @@ -1081,7 +1083,12 @@ proc track(tracked: PEffects, n: PNode) = for i in 0.. 0: - incl(result.flags, tfNeedsInit) + incl(result.flags, tfRequiresInit) elif n[1].kind in {nkCharLit..nkUInt64Lit} and n[1].intVal < 0: - incl(result.flags, tfNeedsInit) + incl(result.flags, tfRequiresInit) elif n[0].kind in {nkFloatLit..nkFloat64Lit} and n[0].floatVal > 0.0: - incl(result.flags, tfNeedsInit) + incl(result.flags, tfRequiresInit) elif n[1].kind in {nkFloatLit..nkFloat64Lit} and n[1].floatVal < 0.0: - incl(result.flags, tfNeedsInit) + incl(result.flags, tfRequiresInit) else: if n[1].kind == nkInfix and considerQuotedIdent(c, n[1][0]).s == "..<": localError(c.config, n[0].info, "range types need to be constructed with '..', '..<' is not supported") @@ -876,6 +877,8 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; isInheritable: bool): PTy pragma(c, s, n[0], typePragmas) if base == nil and tfInheritable notin result.flags: incl(result.flags, tfFinal) + if c.inGenericContext == 0 and computeRequiresInit(c, result): + result.flags.incl tfRequiresInit proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = if n.len < 1: @@ -1717,12 +1720,43 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = case n.len of 3: result = semTypeNode(c, n[1], prev) - if result.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).kind in NilableTypes+GenericTypes+{tyForward} and - n[2].kind == nkNilLit: + if result.kind == tyTypeDesc and tfUnresolved notin result.flags: + result = result.base + if n[2].kind != nkNilLit: + localError(c.config, n.info, + "Invalid syntax. When used with a type, 'not' can be followed only by 'nil'") + if notnil notin c.features: + localError(c.config, n.info, + "enable the 'not nil' annotation with {.experimental: \"notnil\".}") + let resolvedType = result.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}) + case resolvedType.kind + of tyGenericParam, tyTypeDesc, tyFromExpr: + # XXX: This is a really inappropraite hack, but it solves + # https://github.com/nim-lang/Nim/issues/4907 for now. + # + # A proper solution is to introduce a new type kind such + # as `tyNotNil[tyRef[SomeGenericParam]]`. This will allow + # semtypinst to replace the generic param correctly in + # situations like the following: + # + # type Foo[T] = object + # bar: ref T not nil + # baz: ref T + # + # The root of the problem is that `T` here must have a specific + # ID that is bound to a concrete type during instantiation. + # The use of `freshType` below breaks this. Another hack would + # be to reuse the same ID for the not nil type, but this will + # fail if the `T` parameter is referenced multiple times as in + # the example above. + # + # I suggest revisiting this once the language decides on whether + # `not nil` should be the default. We can then map nilable refs + # to other types such as `Option[T]`. + result = makeTypeFromExpr(c, newTree(nkStmtListType, n.copyTree)) + of NilableTypes + {tyGenericInvocation, tyForward}: result = freshType(result, prev) result.flags.incl(tfNotNil) - if notnil notin c.features: - localError(c.config, n.info, "enable the 'not nil' annotation with {.experimental: \"notnil\".}") else: localError(c.config, n.info, errGenerated, "invalid type") of 2: diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 5aec3eed9039f..08ea85708aa43 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -613,6 +613,8 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = of tyObject, tyTuple: propagateFieldFlags(result, result.n) + if result.kind == tyObject and cl.c.computeRequiresInit(cl.c, result): + result.flags.incl tfRequiresInit of tyProc: eraseVoidParams(result) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 79b3ea94c5384..04ceb47b8221d 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1937,7 +1937,9 @@ proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType, var call = newNodeI(nkCall, arg.info) call.add(f.n.copyTree) call.add(arg.copyTree) - result = c.semTryExpr(c, call) + # XXX: This would be much nicer if we don't use `semTryExpr` and + # instead we directly search for overloads with `resolveOverloads`: + result = c.semTryExpr(c, call, {efNoSem2Check}) if result != nil: if result.typ == nil: return nil diff --git a/tests/collections/ttables.nim b/tests/collections/ttables.nim index a59707865d07c..2a590dd26ce5b 100644 --- a/tests/collections/ttables.nim +++ b/tests/collections/ttables.nim @@ -80,7 +80,7 @@ block thashes: # Test with range block: type - R = range[1..10] + R = range[0..9] var t = initTable[R,int]() # causes warning, why? t[1] = 42 # causes warning, why? t[2] = t[1] + 1 diff --git a/tests/constructors/tinvalid_construction.nim b/tests/constructors/tinvalid_construction.nim index b3e56eec6087d..4b372d68a61a7 100644 --- a/tests/constructors/tinvalid_construction.nim +++ b/tests/constructors/tinvalid_construction.nim @@ -3,11 +3,16 @@ template accept(x) = template reject(x) = static: assert(not compiles(x)) + {.experimental: "notnil".} + type TRefObj = ref object x: int + IllegalToConstruct = object + x: cstring not nil + THasNotNils = object of RootObj a: TRefObj not nil b: TRefObj not nil @@ -15,6 +20,8 @@ type THasNotNilsRef = ref THasNotNils + TRefObjNotNil = TRefObj not nil + TChoice = enum A, B, C, D, E, F TBaseHasNotNils = object of THasNotNils @@ -26,6 +33,22 @@ type else: discard + PartialRequiresInit = object + a {.requiresInit.}: int + b: string + + PartialRequiresInitRef = ref PartialRequiresInit + + FullRequiresInit {.requiresInit.} = object + a: int + b: int + + FullRequiresInitRef = ref FullRequiresInit + + FullRequiresInitWithParent {.requiresInit.} = object of THasNotNils + e: int + d: int + TObj = object case choice: TChoice of A: @@ -55,11 +78,23 @@ type var x = D var nilRef: TRefObj -var notNilRef = TRefObj(x: 20) +let notNilRef = TRefObjNotNil(x: 20) proc makeHasNotNils: ref THasNotNils = - result.a = TRefObj(x: 10) - result.b = TRefObj(x: 20) + (ref THasNotNils)(a: TRefObj(x: 10), + b: TRefObj(x: 20)) + +proc userDefinedDefault(T: typedesc): T = + # We'll use that to make sure the user cannot cheat + # with constructing requiresInit types + discard + +proc genericDefault(T: typedesc): T = + result = default(T) + +reject IllegalToConstruct() +reject: + var x: IllegalToConstruct accept TObj() accept TObj(choice: A) @@ -74,14 +109,22 @@ reject TObj(a: 10, f: "") # conflicting fields accept TObj(choice: E, e1: TRefObj(x: 10), e2: 10) accept THasNotNils(a: notNilRef, b: notNilRef, c: nilRef) -# XXX: the "not nil" logic in the compiler is not strong enough to catch this one yet: -# reject THasNotNils(a: notNilRef, b: nilRef, c: nilRef) +reject THasNotNils(a: notNilRef, b: nilRef, c: nilRef) # `b` shouldn't be nil reject THasNotNils(b: notNilRef, c: notNilRef) # there is a missing not nil field reject THasNotNils() # again, missing fields accept THasNotNils(a: notNilRef, b: notNilRef) # it's OK to omit a non-mandatory field +# produces only warning: reject default(THasNotNils) +# produces only warning: reject userDefinedDefault(THasNotNils) + +# produces only warning: reject default(TRefObjNotNil) +# produces only warning: reject userDefinedDefault(TRefObjNotNil) +# produces only warning: reject genericDefault(TRefObjNotNil) # missing not nils in base reject TBaseHasNotNils() +# produces only warning: reject default(TBaseHasNotNils) +# produces only warning: reject userDefinedDefault(TBaseHasNotNils) +# produces only warning: reject genericDefault(TBaseHasNotNils) # once you take care of them, it's ok accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: D) @@ -106,8 +149,118 @@ 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 PartialRequiresInitRef(a: 10, b: "x") +accept PartialRequiresInitRef(a: 20) +reject PartialRequiresInitRef(b: "x") +reject PartialRequiresInitRef() +accept((ref PartialRequiresInit)(a: 10, b: "x")) +accept((ref PartialRequiresInit)(a: 20)) +reject((ref PartialRequiresInit)(b: "x")) +reject((ref PartialRequiresInit)()) + +# produces only warning: reject default(PartialRequiresInit) +# produces only warning: reject userDefinedDefault(PartialRequiresInit) +reject: + var obj: PartialRequiresInit + +accept FullRequiresInit(a: 10, b: 20) +reject FullRequiresInit(a: 10) +reject FullRequiresInit(b: 20) +reject FullRequiresInit() +accept FullRequiresInitRef(a: 10, b: 20) +reject FullRequiresInitRef(a: 10) +reject FullRequiresInitRef(b: 20) +reject FullRequiresInitRef() +accept((ref FullRequiresInit)(a: 10, b: 20)) +reject((ref FullRequiresInit)(a: 10)) +reject((ref FullRequiresInit)(b: 20)) +reject((ref FullRequiresInit)()) + +# produces only warning: reject default(FullRequiresInit) +# produces only warning: reject userDefinedDefault(FullRequiresInit) +reject: + var obj: FullRequiresInit + +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() +# produces only warning: reject default(FullRequiresInitWithParent) +# produces only warning: reject userDefinedDefault(FullRequiresInitWithParent) +reject: + var obj: FullRequiresInitWithParent + # this will be accepted, because the false outer branch will be taken and the inner A branch accept TNestedChoices() +accept default(TNestedChoices) +accept: + var obj: TNestedChoices + +#[# produces only warning: +reject: + # This proc is illegal, because it tries to produce + # a default object of a type that requires initialization: + proc defaultHasNotNils: THasNotNils = + discard +#]# + +#[# produces only warning: +reject: + # You cannot cheat by using the result variable to specify + # only some of the fields + proc invalidPartialTHasNotNils: THasNotNils = + result.c = nilRef +#]# + +#[# produces only warning: +reject: + # The same applies for requiresInit types + proc invalidPartialRequiersInit: PartialRequiresInit = + result.b = "x" +#]# + +#[# produces only warning: +# All code paths must return a value when the result requires initialization: +reject: + proc ifWithoutAnElse: THasNotNils = + if stdin.readLine == "": + return THasNotNils(a: notNilRef, b: notNilRef, c: nilRef) +#]# + +accept: + # All code paths must return a value when the result requires initialization: + proc wellFormedIf: THasNotNils = + if stdin.readLine == "": + return THasNotNils(a: notNilRef, b: notNilRef, c: nilRef) + else: + return THasNotNIls(a: notNilRef, b: notNilRef) + +#[# produces only warning: +reject: + proc caseWithoutAllCasesCovered: FullRequiresInit = + # Please note that these is no else branch here: + case stdin.readLine + of "x": + return FullRequiresInit(a: 10, b: 20) + of "y": + return FullRequiresInit(a: 30, b: 40) +#]# + +accept: + proc wellFormedCase: FullRequiresInit = + case stdin.readLine + of "x": + result = FullRequiresInit(a: 10, b: 20) + else: + # Mixing result and return is fine: + return FullRequiresInit(a: 30, b: 40) # but if we supply a run-time value for the inner branch, the compiler won't be able to prove # that the notnil field was initialized @@ -120,3 +273,167 @@ reject TNestedChoices(outerChoice: false, innerChoice: C) accept TNestedChoices(outerChoice: false, innerChoice: C, notnil: notNilRef) reject TNestedChoices(outerChoice: false, innerChoice: C, notnil: nil) +# Tests involving generics and sequences: +# +block: + # This test aims to show that it's possible to instantiate and + # use a sequence with a requiresInit type: + + var legalSeq: seq[IllegalToConstruct] + legalSeq.add IllegalToConstruct(x: "one") + var two = IllegalToConstruct(x: "two") + legalSeq.add two + var one = legalSeq[0] + var twoAgain = legalSeq.pop + + #[# produces only warning: + # It's not possible to tell the sequence to create elements + # for us though: + reject: + var illegalSeq = newSeq[IllegalToConstruct](10) + #]# + + #[# produces only warning: + reject: + var illegalSeq: seq[IllegalToConstruct] + newSeq(illegalSeq, 10) + #]# + + #[# produces only warning: + reject: + var illegalSeq: seq[IllegalToConstruct] + illegalSeq.setLen 10 + #]# + + # You can still use newSeqOfCap to write efficient code: + var anotherLegalSequence = newSeqOfCap[IllegalToConstruct](10) + for i in 0..9: + anotherLegalSequence.add IllegalToConstruct(x: "x") + +type DefaultConstructible[yesOrNo: static[bool]] = object + when yesOrNo: + x: string + else: + x: cstring not nil + +block: + # Constructability may also depend on the generic parameters of the type: + accept: + var a: DefaultConstructible[true] + var b = DefaultConstructible[true]() + var c = DefaultConstructible[true](x: "test") + var d = DefaultConstructible[false](x: "test") + + reject: + var y: DefaultConstructible[false] + + reject: + var y = DefaultConstructible[false]() + +block: + type + Hash = int + + HashTableSlotType = enum + Free = Hash(0) + Deleted = Hash(1) + HasKey = Hash(2) + + KeyValuePair[A, B] = object + key: A + case hash: HashTableSlotType + of Free, Deleted: + discard + else: + value: B + + # The above KeyValuePair is an interesting type because it + # may become unconstructible depending on the generic parameters: + accept KeyValuePair[int, string](hash: Deleted) + accept KeyValuePair[int, IllegalToConstruct](hash: Deleted) + + accept KeyValuePair[int, string](hash: HasKey) + reject KeyValuePair[int, IllegalToConstruct](hash: HasKey) + + # Since all the above variations don't have a non-constructible + # field in the default branch of the case object, we can construct + # such values: + accept KeyValuePair[int, string]() + accept KeyValuePair[int, IllegalToConstruct]() + accept KeyValuePair[DefaultConstructible[true], string]() + accept KeyValuePair[DefaultConstructible[true], IllegalToConstruct]() + + var a: KeyValuePair[int, string] + var b: KeyValuePair[int, IllegalToConstruct] + var c: KeyValuePair[DefaultConstructible[true], string] + var d: KeyValuePair[DefaultConstructible[true], IllegalToConstruct] + var s1 = newSeq[KeyValuePair[int, IllegalToConstruct]](10) + var s2 = newSeq[KeyValuePair[DefaultConstructible[true], IllegalToConstruct]](10) + + # But let's put the non-constructible values as keys: + reject KeyValuePair[IllegalToConstruct, int](hash: Deleted) + reject KeyValuePair[IllegalToConstruct, int]() + + type IllegalPair = KeyValuePair[DefaultConstructible[false], string] + + reject: + var x: IllegalPair + + #[# produces only warning: + reject: + var s = newSeq[IllegalPair](10) + #]# + +# Specific issues: +# +block: + # https://github.com/nim-lang/Nim/issues/11428 + type + Enum = enum A, B, C + Thing = object + case kind: Enum + of A: discard + of B: s: string + of C: r: range[1..1] # DateTime + + # Fine to not initialize 'r' because this is implicitly initialized and known to be branch 'A'. + var x = Thing() + discard x + +block: + # https://github.com/nim-lang/Nim/issues/4907 + type + Foo = ref object + Bar = object + + Thing[A, B] = ref object + a: A not nil + b: ref B + c: ref B not nil + + proc allocNotNil(T: typedesc): T not nil = + new result + + proc mutateThing(t: var Thing[Foo, Bar]) = + let fooNotNil = allocNotNil(Foo) + var foo: Foo + + let barNotNil = allocNotNil(ref Bar) + var bar: ref Bar + + t.a = fooNotNil + t.b = bar + t.b = barNotNil + t.c = barNotNil + + reject: + t.a = foo + + reject: + t.c = bar + + var thing = Thing[Foo, Bar](a: allocNotNil(Foo), + b: allocNotNil(ref Bar), + c: allocNotNil(ref Bar)) + mutateThing thing + diff --git a/tests/discard/tneedsdiscard_in_for.nim b/tests/discard/tneedsdiscard_in_for.nim index 499b06009176e..ab5216150ece0 100644 --- a/tests/discard/tneedsdiscard_in_for.nim +++ b/tests/discard/tneedsdiscard_in_for.nim @@ -8,7 +8,7 @@ type Rgba8 = object proc premultiply*(c: var Rgba8): var Rgba8 = - discard + return c type App = ref object diff --git a/tests/notnil/tnotnil_in_objconstr.nim b/tests/notnil/tnotnil_in_objconstr.nim index fb8ac8d6f350b..471150f44625a 100644 --- a/tests/notnil/tnotnil_in_objconstr.nim +++ b/tests/notnil/tnotnil_in_objconstr.nim @@ -1,13 +1,17 @@ discard """ - errormsg: "fields not initialized: bar" - line: "13" + errormsg: "The Foo type requires the following fields to be initialized: bar, baz" + line: "17" """ {.experimental: "notnil".} # bug #2355 type - Foo = object + Base = object of RootObj + baz: ref int not nil + + Foo = object of Base foo: ref int bar: ref int not nil + var x: ref int = new(int) # Create instance without initializing the `bar` field var f = Foo(foo: x) diff --git a/tests/objects/tobjects_issues.nim b/tests/objects/tobjects_issues.nim index fddfff7d51447..f1a416d04838d 100644 --- a/tests/objects/tobjects_issues.nim +++ b/tests/objects/tobjects_issues.nim @@ -113,5 +113,5 @@ block t3038: Type = ref object of RootObj SubType[T] = ref object of Type data: Data[T] - SubSubType = ref object of SubType + SubSubType = ref object of SubType[int] SubSubSubType = ref object of SubSubType diff --git a/tests/types/temptyseqs.nim b/tests/types/temptyseqs.nim index 28c3b87b837c3..6e7b58cedaa0b 100644 --- a/tests/types/temptyseqs.nim +++ b/tests/types/temptyseqs.nim @@ -75,16 +75,16 @@ varargso("foo", "bar", "baz") type Flago = enum - tfNeedsInit, tfNotNil + tfRequiresInit, tfNotNil -var s: set[Flago] = {tfNeedsInit} +var s: set[Flago] = {tfRequiresInit} -if {tfNeedsInit, tfNotNil} * s != {}: +if {tfRequiresInit, tfNotNil} * s != {}: echo "yes" else: echo "no" -if {tfNeedsInit, tfNotNil} * s <= {tfNotNil}: +if {tfRequiresInit, tfNotNil} * s <= {tfNotNil}: echo "yes" else: echo "no"