Implement language feature #6885 (#6954)

This commit is contained in:
cooldome 2017-12-25 00:22:03 +03:00 • committed by Andreas Rumpf
commit 2b3ec0a7c6
8 changed files with 131 additions and 14 deletions

View file

@ -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 - codegenDecl pragma now works for the JavaScript backend. It returns an empty string for
function return type placeholders. function return type placeholders.
- Asynchronous programming for the JavaScript backend using the `asyncjs` module. - 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")
```

View file

@ -26,7 +26,8 @@ type
errAtPopWithoutPush, errEmptyAsm, errInvalidIndentation, errAtPopWithoutPush, errEmptyAsm, errInvalidIndentation,
errExceptionExpected, errExceptionAlreadyHandled, errExceptionExpected, errExceptionAlreadyHandled,
errYieldNotAllowedHere, errYieldNotAllowedInTryStmt, errYieldNotAllowedHere, errYieldNotAllowedInTryStmt,
errInvalidNumberOfYieldExpr, errCannotReturnExpr, errAttemptToRedefine, errInvalidNumberOfYieldExpr, errCannotReturnExpr,
errNoReturnWithReturnTypeNotAllowed, errAttemptToRedefine,
errStmtInvalidAfterReturn, errStmtExpected, errInvalidLabel, errStmtInvalidAfterReturn, errStmtExpected, errInvalidLabel,
errInvalidCmdLineOption, errCmdLineArgExpected, errCmdLineNoArgExpected, errInvalidCmdLineOption, errCmdLineArgExpected, errCmdLineNoArgExpected,
errInvalidVarSubstitution, errUnknownVar, errUnknownCcompiler, errInvalidVarSubstitution, errUnknownVar, errUnknownCcompiler,
@ -179,8 +180,9 @@ const
errYieldNotAllowedInTryStmt: "'yield' cannot be used within 'try' in a non-inlined iterator", errYieldNotAllowedInTryStmt: "'yield' cannot be used within 'try' in a non-inlined iterator",
errInvalidNumberOfYieldExpr: "invalid number of \'yield\' expressions", errInvalidNumberOfYieldExpr: "invalid number of \'yield\' expressions",
errCannotReturnExpr: "current routine cannot return an expression", errCannotReturnExpr: "current routine cannot return an expression",
errNoReturnWithReturnTypeNotAllowed: "routines with NoReturn pragma are not allowed to have return type",
errAttemptToRedefine: "redefinition of \'$1\'", 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", errStmtExpected: "statement expected",
errInvalidLabel: "\'$1\' is no label", errInvalidLabel: "\'$1\' is no label",
errInvalidCmdLineOption: "invalid command line option: \'$1\'", errInvalidCmdLineOption: "invalid command line option: \'$1\'",

View file

@ -771,6 +771,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
of wNoreturn: of wNoreturn:
noVal(it) noVal(it)
incl(sym.flags, sfNoReturn) incl(sym.flags, sfNoReturn)
if sym.ast[paramsPos][0].kind != nkEmpty:
localError(sym.ast[paramsPos][0].info, errNoReturnWithReturnTypeNotAllowed)
of wDynlib: of wDynlib:
processDynLib(c, it, sym) processDynLib(c, it, sym)
of wCompilerproc: of wCompilerproc:

View file

@ -165,6 +165,19 @@ proc commonType*(x, y: PType): PType =
result = newType(k, r.owner) result = newType(k, r.owner)
result.addSonSkipIntLit(r) 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 = proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym =
result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info) result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info)
when defined(nimsuggest): when defined(nimsuggest):

View file

@ -165,14 +165,14 @@ proc semIf(c: PContext, n: PNode): PNode =
it.sons[0] = forceBool(c, semExprWithType(c, it.sons[0])) it.sons[0] = forceBool(c, semExprWithType(c, it.sons[0]))
when not newScopeForIf: openScope(c) when not newScopeForIf: openScope(c)
it.sons[1] = semExprBranch(c, it.sons[1]) it.sons[1] = semExprBranch(c, it.sons[1])
typ = commonType(typ, it.sons[1].typ) typ = commonType(typ, it.sons[1])
closeScope(c) closeScope(c)
elif it.len == 1: elif it.len == 1:
hasElse = true hasElse = true
it.sons[0] = semExprBranchScope(c, it.sons[0]) it.sons[0] = semExprBranchScope(c, it.sons[0])
typ = commonType(typ, it.sons[0].typ) typ = commonType(typ, it.sons[0])
else: illFormedAst(it) 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) for it in n: discardCheck(c, it.lastSon)
result.kind = nkIfStmt result.kind = nkIfStmt
# propagate any enforced VoidContext: # propagate any enforced VoidContext:
@ -180,7 +180,8 @@ proc semIf(c: PContext, n: PNode): PNode =
else: else:
for it in n: for it in n:
let j = it.len-1 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.kind = nkIfExpr
result.typ = typ result.typ = typ
@ -213,7 +214,7 @@ proc semCase(c: PContext, n: PNode): PNode =
semCaseBranch(c, n, x, i, covered) semCaseBranch(c, n, x, i, covered)
var last = sonsLen(x)-1 var last = sonsLen(x)-1
x.sons[last] = semExprBranchScope(c, x.sons[last]) x.sons[last] = semExprBranchScope(c, x.sons[last])
typ = commonType(typ, x.sons[last].typ) typ = commonType(typ, x.sons[last])
of nkElifBranch: of nkElifBranch:
chckCovered = false chckCovered = false
checkSonsLen(x, 2) checkSonsLen(x, 2)
@ -221,13 +222,13 @@ proc semCase(c: PContext, n: PNode): PNode =
x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0])) x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0]))
when not newScopeForIf: openScope(c) when not newScopeForIf: openScope(c)
x.sons[1] = semExprBranch(c, x.sons[1]) x.sons[1] = semExprBranch(c, x.sons[1])
typ = commonType(typ, x.sons[1].typ) typ = commonType(typ, x.sons[1])
closeScope(c) closeScope(c)
of nkElse: of nkElse:
chckCovered = false chckCovered = false
checkSonsLen(x, 1) checkSonsLen(x, 1)
x.sons[0] = semExprBranchScope(c, x.sons[0]) x.sons[0] = semExprBranchScope(c, x.sons[0])
typ = commonType(typ, x.sons[0].typ) typ = commonType(typ, x.sons[0])
hasElse = true hasElse = true
else: else:
illFormedAst(x) illFormedAst(x)
@ -237,7 +238,7 @@ proc semCase(c: PContext, n: PNode): PNode =
else: else:
localError(n.info, errNotAllCasesCovered) localError(n.info, errNotAllCasesCovered)
closeScope(c) 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) for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
# propagate any enforced VoidContext: # propagate any enforced VoidContext:
if typ == enforceVoidContext: if typ == enforceVoidContext:
@ -246,7 +247,8 @@ proc semCase(c: PContext, n: PNode): PNode =
for i in 1..n.len-1: for i in 1..n.len-1:
var it = n.sons[i] var it = n.sons[i]
let j = it.len-1 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 result.typ = typ
proc semTry(c: PContext, n: PNode): PNode = proc semTry(c: PContext, n: PNode): PNode =
@ -1851,8 +1853,8 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
else: else:
n.typ = n.sons[i].typ n.typ = n.sons[i].typ
if not isEmptyType(n.typ): n.kind = nkStmtListExpr if not isEmptyType(n.typ): n.kind = nkStmtListExpr
case n.sons[i].kind if n.sons[i].kind in LastBlockStmts or
of LastBlockStmts: 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): for j in countup(i + 1, length - 1):
case n.sons[j].kind case n.sons[j].kind
of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr, of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr,

View file

@ -63,7 +63,6 @@ proc chckObj(obj, subclass: PNimType) {.compilerproc.} =
while x != subclass: while x != subclass:
if x == nil: if x == nil:
sysFatal(ObjectConversionError, "invalid object conversion") sysFatal(ObjectConversionError, "invalid object conversion")
break
x = x.base x = x.base
proc chckObjAsgn(a, b: PNimType) {.compilerproc, inline.} = proc chckObjAsgn(a, b: PNimType) {.compilerproc, inline.} =

View file

@ -36,5 +36,64 @@ var z = case i
echo z echo z
#OUT ayyy #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")
))

View file

@ -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
))