From 564c0acae20419133f3fadd17be4bae42dd408d9 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sat, 18 Mar 2017 17:22:26 +0200 Subject: [PATCH] cleaned up the code and implemented proper error messages --- compiler/sem.nim | 16 +- compiler/semdata.nim | 15 +- compiler/semexprs.nim | 259 ++++++++++++++++++-------- tests/notnil/tnotnil_in_objconstr.nim | 2 +- 4 files changed, 209 insertions(+), 83 deletions(-) diff --git a/compiler/sem.nim b/compiler/sem.nim index 57b87e0bb4..ab3c5cec8a 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -45,7 +45,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode proc activate(c: PContext, n: PNode) proc semQuoteAst(c: PContext, n: PNode): PNode proc finishMethod(c: PContext, s: PSym) - +proc evalAtCompileTime(c: PContext, n: PNode): PNode proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode proc isArrayConstr(n: PNode): bool {.inline.} = @@ -328,6 +328,20 @@ proc semConstExpr(c: PContext, n: PNode): PNode = else: result = fixupTypeAfterEval(c, result, e) +proc semExprFlagDispatched(c: PContext, n: PNode, flags: TExprFlags): PNode = + if efNeedStatic in flags: + if efPreferNilResult in flags: + return tryConstExpr(c, n) + else: + return semConstExpr(c, n) + else: + result = semExprWithType(c, n, flags) + if efPreferStatic in flags: + var evaluated = getConstExpr(c.module, result) + if evaluated != nil: return evaluated + evaluated = evalAtCompileTime(c, result) + if evaluated != nil: return evaluated + include hlo, seminst, semcall when false: diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 5c24274012..3abfeac218 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -45,8 +45,19 @@ type inst*: PInstantiation TExprFlag* = enum - efLValue, efWantIterator, efWantStatic, efInTypeof, - efWantStmt, efAllowStmt, efDetermineType, efExplain, + efLValue, efWantIterator, efInTypeof, + efNeedStatic, + # Use this in contexts where a static value is mandatory + efPreferStatic, + # Use this in contexts where a static value could bring more + # information, but it's not strictly mandatory. This may become + # the default with implicit statics in the future. + efPreferNilResult, + # Use this if you want a certain result (e.g. static value), + # but you don't want to trigger a hard error. For example, + # you may be in position to supply a better error message + # to the user. + efWantStmt, efAllowStmt, efDetermineType, exExplain, efAllowDestructor, efWantValue, efOperand, efNoSemCheck, efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo, diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 032b146d3e..f80f671607 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2098,24 +2098,17 @@ proc isTupleType(n: PNode): bool = return true type - InitializationResult = enum + InitStatus = enum initUnknown initFull # All of the fields have been initialized initPartial # Some of the fields have been initialized initNone # None of the fields have been initialized initConflict # Fields from different branches have been initialized - InitStatus = object - status: InitializationResult - missingFields: seq[int] - conflictingFields: seq[int] - assumptions: seq[(int, PNode)] # field and expected value - -proc mergeInitStatus(existing: var InitializationResult, - newStatus: InitializationResult) = +proc mergeInitStatus(existing: var InitStatus, newStatus: InitStatus) = case newStatus of initConflict: - existing = initConflict + existing = newStatus of initPartial: if existing in {initUnknown, initFull, initNone}: existing = initPartial @@ -2132,103 +2125,211 @@ proc mergeInitStatus(existing: var InitializationResult, of initUnknown: discard +proc locateFieldInInitExpr(field: PSym, initExpr: PNode): PNode = + # Returns the assignment nkExprColonExpr node or nil + let fieldId = field.name.id + for i in 1 .. = 0 and selectedBranch < recNode.len - 1: - let discriminatorVal = semConstrField(c, flags + {efWantStatic}, - discriminator.sym, initExpr) + if selectedBranch != -1: + let branchNode = recNode[selectedBranch] + let flags = flags*{efAllowDestructor} + {efNeedStatic, efPreferNilResult} + let discriminatorVal = semConstrField(c, flags, + discriminator.sym, initExpr) if discriminatorVal == nil: - localError(initExpr.info, "discrimantor not const") - elif not discriminatorVal.matchesAnyCaseInNkOf(recNode[selectedBranch]): - localError(initExpr.info, "wrong branch taken") + let fields = fieldsPresentInBranch(selectedBranch) + localError(initExpr.info, + "the discriminator '$1' appearing in the construction of a case " & + "object must be a compile-time value in order to prove that it's " & + "initialize field(s) $2.", + [discriminator.sym.name.s, fields]) + mergeInitStatus(result, initNone) + else: + let discriminatorVal = discriminatorVal.skipHidden + + template wrongBranchError(i) = + let fields = fieldsPresentInBranch(i) + localError(initExpr.info, + "a case selecting discriminator '$1' with value '$2' " & + "appears in the object construction, but the field(s) $3 " & + "are in conflict with this value.", + [discriminator.sym.name.s, discriminatorVal.renderTree, fields]) + + if branchNode.kind != nkElse: + if not branchNode.caseBranchMatchesExpr(discriminatorVal): + wrongBranchError(selectedBranch) + else: + # With an else clause, check that all other branches don't match: + for i in 1 .. (recNode.len - 2): + if recNode[i].caseBranchMatchesExpr(discriminatorVal): + wrongBranchError(i) + break + + # When a branch is selected with a partial match, some of the fields + # that were not initialized may be mandatory. We must check for this: + if result == initPartial: + checkMissingFields branchNode + else: - result.status = initNone - let f = semConstrField(c, flags, discriminator.sym, initExpr) - mergeInitStatus(result.status, if f != nil: initFull else: initNone) + result = initNone + let discriminatorVal = semConstrField(c, flags + {efPreferStatic}, + discriminator.sym, initExpr) + if discriminatorVal == nil: + # None of the branches were explicitly selected by the user and no + # value was given to the discrimator. We can assume that it will be + # initialized to zero and this will select a particular branch as + # a result: + let matchedBranch = recNode.pickCaseBranch newIntLit(0) + checkMissingFields matchedBranch + else: + result = initPartial + if discriminatorVal.kind == nkIntLit: + # When the discriminator is a compile-time value, we also know + # which brach will be selected: + let matchedBranch = recNode.pickCaseBranch discriminatorVal + if matchedBranch != nil: checkMissingFields matchedBranch + else: + # All bets are off. If any of the branches has a mandatory + # fields we must produce an error: + for i in 1 ..