summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md22
-rw-r--r--compiler/msgs.nim6
-rw-r--r--compiler/pragmas.nim2
-rw-r--r--compiler/sem.nim13
-rw-r--r--compiler/semstmts.nim24
-rw-r--r--lib/system/chcks.nim1
-rw-r--r--tests/casestmt/tcasestm.nim59
-rw-r--r--tests/pragmas/tnoreturn.nim18
8 files changed, 131 insertions, 14 deletions
diff --git a/changelog.md b/changelog.md
index efdd1f520..5734a4cb1 100644
--- a/changelog.md
+++ b/changelog.md
@@ -144,3 +144,25 @@ This now needs to be written as:
 - codegenDecl pragma now works for the JavaScript backend. It returns an empty string for
   function return type placeholders.
 - Asynchronous programming for the JavaScript backend using the `asyncjs` module.
+- Extra semantic checks for procs with noreturn pragma: return type is not allowed,
+  statements after call to noreturn procs are no longer allowed.
+- Noreturn proc calls and raising exceptions branches are now skipped during common type 
+  deduction in if and case expressions. The following code snippets now compile:
+```nim
+import strutils
+let str = "Y"
+let a = case str:
+  of "Y": true
+  of "N": false
+  else: raise newException(ValueError, "Invalid boolean")
+let b = case str:
+  of nil, "": raise newException(ValueError, "Invalid boolean")
+  elif str.startsWith("Y"): true
+  elif str.startsWith("N"): false
+  else: false 
+let c = if str == "Y": true 
+  elif str == "N": false 
+  else:
+    echo "invalid bool" 
+    quit("this is the end")
+```
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index 2668c72ae..4e6226122 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -26,7 +26,8 @@ type
     errAtPopWithoutPush, errEmptyAsm, errInvalidIndentation,
     errExceptionExpected, errExceptionAlreadyHandled,
     errYieldNotAllowedHere, errYieldNotAllowedInTryStmt,
-    errInvalidNumberOfYieldExpr, errCannotReturnExpr, errAttemptToRedefine,
+    errInvalidNumberOfYieldExpr, errCannotReturnExpr, 
+    errNoReturnWithReturnTypeNotAllowed, errAttemptToRedefine,
     errStmtInvalidAfterReturn, errStmtExpected, errInvalidLabel,
     errInvalidCmdLineOption, errCmdLineArgExpected, errCmdLineNoArgExpected,
     errInvalidVarSubstitution, errUnknownVar, errUnknownCcompiler,
@@ -179,8 +180,9 @@ const
     errYieldNotAllowedInTryStmt: "'yield' cannot be used within 'try' in a non-inlined iterator",
     errInvalidNumberOfYieldExpr: "invalid number of \'yield\' expressions",
     errCannotReturnExpr: "current routine cannot return an expression",
+    errNoReturnWithReturnTypeNotAllowed: "routines with NoReturn pragma are not allowed to have return type",
     errAttemptToRedefine: "redefinition of \'$1\'",
-    errStmtInvalidAfterReturn: "statement not allowed after \'return\', \'break\', \'raise\' or \'continue'",
+    errStmtInvalidAfterReturn: "statement not allowed after \'return\', \'break\', \'raise\', \'continue\' or proc call with noreturn pragma",
     errStmtExpected: "statement expected",
     errInvalidLabel: "\'$1\' is no label",
     errInvalidCmdLineOption: "invalid command line option: \'$1\'",
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index b598cadb2..35fedf4ea 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -771,6 +771,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
       of wNoreturn:
         noVal(it)
         incl(sym.flags, sfNoReturn)
+        if sym.ast[paramsPos][0].kind != nkEmpty:
+          localError(sym.ast[paramsPos][0].info, errNoReturnWithReturnTypeNotAllowed)
       of wDynlib:
         processDynLib(c, it, sym)
       of wCompilerproc:
diff --git a/compiler/sem.nim b/compiler/sem.nim
index bc994201d..ababbd303 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -165,6 +165,19 @@ proc commonType*(x, y: PType): PType =
         result = newType(k, r.owner)
         result.addSonSkipIntLit(r)
 
+proc endsInNoReturn(n: PNode): bool =
+  # check if expr ends in raise exception or call of noreturn proc
+  var it = n
+  while it.kind in {nkStmtList, nkStmtListExpr} and it.len > 0: 
+    it = it.lastSon
+  result = it.kind == nkRaiseStmt or 
+    it.kind in nkCallKinds and it[0].kind == nkSym and sfNoReturn in it[0].sym.flags
+
+proc commonType*(x: PType, y: PNode): PType =
+  # ignore exception raising branches in case/if expressions
+  if endsInNoReturn(y): return x
+  commonType(x, y.typ)
+
 proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym =
   result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info)
   when defined(nimsuggest):
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 8ed120c98..b1fa8c19b 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -165,14 +165,14 @@ proc semIf(c: PContext, n: PNode): PNode =
       it.sons[0] = forceBool(c, semExprWithType(c, it.sons[0]))
       when not newScopeForIf: openScope(c)
       it.sons[1] = semExprBranch(c, it.sons[1])
-      typ = commonType(typ, it.sons[1].typ)
+      typ = commonType(typ, it.sons[1])
       closeScope(c)
     elif it.len == 1:
       hasElse = true
       it.sons[0] = semExprBranchScope(c, it.sons[0])
-      typ = commonType(typ, it.sons[0].typ)
+      typ = commonType(typ, it.sons[0])
     else: illFormedAst(it)
-  if isEmptyType(typ) or typ.kind == tyNil or not hasElse:
+  if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse:
     for it in n: discardCheck(c, it.lastSon)
     result.kind = nkIfStmt
     # propagate any enforced VoidContext:
@@ -180,7 +180,8 @@ proc semIf(c: PContext, n: PNode): PNode =
   else:
     for it in n:
       let j = it.len-1
-      it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
+      if not endsInNoReturn(it.sons[j]):
+        it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
     result.kind = nkIfExpr
     result.typ = typ
 
@@ -213,7 +214,7 @@ proc semCase(c: PContext, n: PNode): PNode =
       semCaseBranch(c, n, x, i, covered)
       var last = sonsLen(x)-1
       x.sons[last] = semExprBranchScope(c, x.sons[last])
-      typ = commonType(typ, x.sons[last].typ)
+      typ = commonType(typ, x.sons[last])
     of nkElifBranch:
       chckCovered = false
       checkSonsLen(x, 2)
@@ -221,13 +222,13 @@ proc semCase(c: PContext, n: PNode): PNode =
       x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0]))
       when not newScopeForIf: openScope(c)
       x.sons[1] = semExprBranch(c, x.sons[1])
-      typ = commonType(typ, x.sons[1].typ)
+      typ = commonType(typ, x.sons[1])
       closeScope(c)
     of nkElse:
       chckCovered = false
       checkSonsLen(x, 1)
       x.sons[0] = semExprBranchScope(c, x.sons[0])
-      typ = commonType(typ, x.sons[0].typ)
+      typ = commonType(typ, x.sons[0])
       hasElse = true
     else:
       illFormedAst(x)
@@ -237,7 +238,7 @@ proc semCase(c: PContext, n: PNode): PNode =
     else:
       localError(n.info, errNotAllCasesCovered)
   closeScope(c)
-  if isEmptyType(typ) or typ.kind == tyNil or not hasElse:
+  if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse:
     for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
     # propagate any enforced VoidContext:
     if typ == enforceVoidContext:
@@ -246,7 +247,8 @@ proc semCase(c: PContext, n: PNode): PNode =
     for i in 1..n.len-1:
       var it = n.sons[i]
       let j = it.len-1
-      it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
+      if not endsInNoReturn(it.sons[j]):
+        it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
     result.typ = typ
 
 proc semTry(c: PContext, n: PNode): PNode =
@@ -1851,8 +1853,8 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
       else:
         n.typ = n.sons[i].typ
         if not isEmptyType(n.typ): n.kind = nkStmtListExpr
-      case n.sons[i].kind
-      of LastBlockStmts:
+      if n.sons[i].kind in LastBlockStmts or
+         n.sons[i].kind in nkCallKinds and n.sons[i][0].kind == nkSym and sfNoReturn in n.sons[i][0].sym.flags:
         for j in countup(i + 1, length - 1):
           case n.sons[j].kind
           of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr,
diff --git a/lib/system/chcks.nim b/lib/system/chcks.nim
index 1520f231e..69b680dbd 100644
--- a/lib/system/chcks.nim
+++ b/lib/system/chcks.nim
@@ -63,7 +63,6 @@ proc chckObj(obj, subclass: PNimType) {.compilerproc.} =
   while x != subclass:
     if x == nil:
       sysFatal(ObjectConversionError, "invalid object conversion")
-      break
     x = x.base
 
 proc chckObjAsgn(a, b: PNimType) {.compilerproc, inline.} =
diff --git a/tests/casestmt/tcasestm.nim b/tests/casestmt/tcasestm.nim
index 7ac20bf2f..b005d8120 100644
--- a/tests/casestmt/tcasestm.nim
+++ b/tests/casestmt/tcasestm.nim
@@ -36,5 +36,64 @@ var z = case i
 echo z
 #OUT ayyy
 
+let str1 = "Y"
+let str2 = "NN"
+let a = case str1:
+  of "Y": true
+  of "N": false
+  else: 
+    echo "no good"
+    quit("quiting")
 
+let b = case str2:
+  of nil, "": raise newException(ValueError, "Invalid boolean")
+  elif str2[0] == 'Y': true
+  elif str2[0] == 'N': false
+  else: "error".quit(2)
 
+doAssert(a == true)
+doAssert(b == false)
+
+var bb: bool
+doassert(not compiles(
+  bb = case str2:
+    of nil, "": raise newException(ValueError, "Invalid boolean")
+    elif str.startsWith("Y"): true
+    elif str.startsWith("N"): false
+))
+
+doassert(not compiles(
+  bb = case str2:
+    of "Y": true
+    of "N": false
+))
+
+doassert(not compiles(
+  bb = case str2:
+    of "Y": true
+    of "N": raise newException(ValueError, "N not allowed")
+))
+
+doassert(not compiles(
+  bb = case str2:
+    of "Y": raise newException(ValueError, "Invalid Y")
+    else: raise newException(ValueError, "Invalid N")
+))
+
+
+doassert(not compiles(
+  bb = case str2:
+    of "Y":
+      raise newException(ValueError, "Invalid Y")
+      true    
+    else: raise newException(ValueError, "Invalid")
+))
+
+
+doassert(not compiles(
+  bb = case str2:
+    of "Y":
+      "invalid Y".quit(3)
+      true    
+    else: raise newException(ValueError, "Invalid")
+))
\ No newline at end of file
diff --git a/tests/pragmas/tnoreturn.nim b/tests/pragmas/tnoreturn.nim
new file mode 100644
index 000000000..2075b352e
--- /dev/null
+++ b/tests/pragmas/tnoreturn.nim
@@ -0,0 +1,18 @@
+discard """
+ccodeCheck: "\\i @'__attribute__((noreturn))' .*"
+"""
+
+proc noret1*(i: int) {.noreturn.} = 
+  echo i
+
+var p {.used.}: proc(i: int): int
+doAssert(not compiles(
+  p = proc(i: int): int {.noreturn.} = i # noreturn lambda returns int
+))
+
+
+doAssert(not compiles(
+  block:
+    noret1(5)
+    echo 1 # statement after noreturn
+))