From 4179f10100401b5e3c40ffdc7e35181fd2d71aed Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Mon, 19 Oct 2020 19:28:08 -0700 Subject: [PATCH] new {.allowMissingCases.} pragma to allow growing enums --- compiler/ast.nim | 1 + compiler/lineinfos.nim | 4 +++- compiler/pragmas.nim | 8 +++++++- compiler/semstmts.nim | 15 +++++++++++---- compiler/semtypes.nim | 10 +++++++--- compiler/wordrecg.nim | 4 ++-- tests/casestmt/tincompletecaseobject.nim | 23 +++++++++++++++++++++++ 7 files changed, 54 insertions(+), 11 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index f796e64c2d88..97f8ed6df62a 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -295,6 +295,7 @@ type sfUsedInFinallyOrExcept # symbol is used inside an 'except' or 'finally' sfSingleUsedTemp # For temporaries that we know will only be used once sfNoalias # 'noalias' annotation, means C's 'restrict' + sfAllowMissingCases # issue warnMissingCases instead of error TSymFlags* = set[TSymFlag] diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index a0b1d1965476..ad46dc27d36b 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -56,6 +56,7 @@ type warnInconsistentSpacing, warnCaseTransition, warnCycleCreated, warnObservableStores, warnUser, + warnMissingCases, hintSuccess, hintSuccessX, hintCC, hintLineTooLong, hintXDeclaredButNotUsed, hintXCannotRaiseY, @@ -126,6 +127,7 @@ const warnCycleCreated: "$1", warnObservableStores: "observable stores to '$1'", warnUser: "$1", + warnMissingCases: "$1", hintSuccess: "operation successful: $#", # keep in sync with `testament.isSuccess` hintSuccessX: "${loc} lines; ${sec}s; $mem; $build build; proj: $project; out: $output", @@ -177,7 +179,7 @@ const "IndexCheck", "GcUnsafe", "GcUnsafe2", "Uninit", "GcMem", "Destructor", "LockLevel", "ResultShadowed", "Spacing", "CaseTransition", "CycleCreated", - "ObservableStores", "User"] + "ObservableStores", "User", "MissingCases"] HintsToStr* = [ "Success", "SuccessX", "CC", "LineTooLong", diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 4d5240e3c2f3..b44c77ac255a 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -63,7 +63,7 @@ const wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow, wIncompleteStruct, wCompleteStruct, wByCopy, wByRef, wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked, - wBorrow, wGcSafe, wPartial, wExplain, wPackage} + wBorrow, wGcSafe, wPartial, wExplain, wPackage, wAllowMissingCases} fieldPragmas* = declPragmas + { wGuard, wBitsize, wCursor, wRequiresInit, wNoalias} - {wExportNims, wNodecl} # why exclude these? varPragmas* = declPragmas + {wVolatile, wRegister, wThreadVar, @@ -875,6 +875,12 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, if sym != nil: if k == wPure and sym.kind in routineKinds: invalidPragma(c, it) else: incl(sym.flags, sfPure) + of wAllowMissingCases: + # let s = expectStrLit(c, it) + noVal(c, it) # TODO: allow allowMissingCases:false + if sym != nil: + incl(sym.flags, sfAllowMissingCases) + else: localError(c.config, it.info, "expected a `sym`") of wVolatile: noVal(c, it) incl(sym.flags, sfVolatile) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index b5e69c135447..9d966a8e7510 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -991,11 +991,18 @@ proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode = if chckCovered: if covered == toCover(c, n[0].typ): hasElse = true - elif n[0].typ.skipTypes(abstractRange).kind in {tyEnum, tyChar}: - localError(c.config, n.info, "not all cases are covered; missing: $1" % - formatMissingEnums(c, n)) else: - localError(c.config, n.info, "not all cases are covered") + let typ = n[0].typ.skipTypes(abstractRange) + template msg: untyped = "not all cases are covered; missing: $1" % formatMissingEnums(c, n) + template bail = localError(c.config, n.info, msg()) + case typ.kind + of tyChar: bail() + of tyEnum: + if sfAllowMissingCases in typ.sym.flags: + message(c.config, n.info, warnMissingCases, msg()) + else: bail() + else: + localError(c.config, n.info, "not all cases are covered") popCaseContext(c) closeScope(c) if isEmptyType(typ) or typ.kind in {tyNil, tyUntyped} or diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 8ef393839a0c..7ed1b59b4b9a 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -701,9 +701,13 @@ proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, delSon(b, b.len - 1) semRecordNodeAux(c, lastSon(n[i]), check, pos, b, rectype, hasCaseFields = true) if chckCovered and covered != toCover(c, a[0].typ): - if a[0].typ.skipTypes(abstractRange).kind == tyEnum: - localError(c.config, a.info, "not all cases are covered; missing: $1" % - formatMissingEnums(c, a)) + let typ = a[0].typ.skipTypes(abstractRange) + if typ.kind == tyEnum: + template msg: untyped = "not all cases are covered; missing: $1" % formatMissingEnums(c, a) + if sfAllowMissingCases in typ.sym.flags: + message(c.config, a.info, warnMissingCases, msg()) + else: + localError(c.config, a.info, msg()) else: localError(c.config, a.info, "not all cases are covered") father.add a diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim index f6fc92dc8fd0..71a0b0aa03f1 100644 --- a/compiler/wordrecg.nim +++ b/compiler/wordrecg.nim @@ -86,7 +86,7 @@ type wStdIn, wStdOut, wStdErr, wInOut, wByCopy, wByRef, wOneWay, - wBitsize + wBitsize, wAllowMissingCases TSpecialWords* = set[TSpecialWord] @@ -175,7 +175,7 @@ const "stdin", "stdout", "stderr", "inout", "bycopy", "byref", "oneway", - "bitsize" + "bitsize", "allowMissingCases" ] proc findStr*(a: openArray[string], s: string): int = diff --git a/tests/casestmt/tincompletecaseobject.nim b/tests/casestmt/tincompletecaseobject.nim index 909ee4e1c3ff..8a0c5be0a74f 100644 --- a/tests/casestmt/tincompletecaseobject.nim +++ b/tests/casestmt/tincompletecaseobject.nim @@ -1,4 +1,8 @@ discard """ +nimout: ''' +tincompletecaseobject.nim(19, 3) Warning: not all cases are covered; missing: {f2} [MissingCases] +tincompletecaseobject.nim(24, 5) Warning: not all cases are covered; missing: {f2} [MissingCases] +''' errormsg: ''' not all cases are covered; missing: {nnkComesFrom, nnkDotCall, nnkHiddenCallConv, nnkVarTuple, nnkCurlyExpr, nnkRange, nnkCheckedFieldExpr, nnkDerefExpr, nnkElifExpr, nnkElseExpr, nnkLambda, nnkDo, nnkBind, nnkClosedSymChoice, nnkHiddenSubConv, nnkConv, nnkStaticExpr, nnkAddr, nnkHiddenAddr, nnkHiddenDeref, nnkObjDownConv, nnkObjUpConv, nnkChckRangeF, nnkChckRange64, nnkChckRange, nnkStringToCString, nnkCStringToString, nnkFastAsgn, nnkGenericParams, nnkFormalParams, nnkOfInherit, nnkImportAs, nnkConverterDef, nnkMacroDef, nnkTemplateDef, nnkIteratorDef, nnkOfBranch, nnkElifBranch, nnkExceptBranch, nnkElse, nnkAsmStmt, nnkTypeDef, nnkFinally, nnkContinueStmt, nnkImportStmt, nnkImportExceptStmt, nnkExportStmt, nnkExportExceptStmt, nnkFromStmt, nnkIncludeStmt, nnkUsingStmt, nnkBlockExpr, nnkStmtListType, nnkBlockType, nnkWith, nnkWithout, nnkTypeOfExpr, nnkObjectTy, nnkTupleTy, nnkTupleClassTy, nnkTypeClassTy, nnkStaticTy, nnkRecList, nnkRecCase, nnkRecWhen, nnkVarTy, nnkConstTy, nnkMutableTy, nnkDistinctTy, nnkProcTy, nnkIteratorTy, nnkSharedTy, nnkEnumTy, nnkEnumFieldDef, nnkArglist, nnkPattern, nnkReturnToken, nnkClosure, nnkGotoState, nnkState, nnkBreakState, nnkFuncDef, nnkTupleConstr} ''' @@ -6,6 +10,25 @@ not all cases are covered; missing: {nnkComesFrom, nnkDotCall, nnkHiddenCallConv # this isn't imported from macros.nim to make it robust against possible changes in the ast. +block: + type Foo {.allowMissingCases.} = enum + f0 + f1 + f2 + var f: Foo + case f + of f0: discard + of f1: discard + + type Bar = object + case k: Foo + of f0: + v0: string + of f1: + v1: string + + var b: Bar + type NimNodeKind* = enum nnkNone, nnkEmpty, nnkIdent, nnkSym,