Skip to content

Commit

Permalink
sem: use nkError in break/continue analysis (#851)
Browse files Browse the repository at this point in the history
## Summary

Split semantic analysis of 'break' and 'continue' statements into two
procedures and use `nkError` propagation instead of `localReport`. The
quality of errors reported for invalid 'break' statements is also
improved.

## Details

* split `semBreakOrContinue` into two procedures, as the handling of
  both had only very little in common
* refactor the analysis of 'break' statements. `ElaborateAst` is used
  for easier error propagation. In addition, the input AST is no longer
  modified
* report errors through `nkError` nodes -- `AstDiagKind` counterparts
  for the previously used reports are added

**Changed errors:**

* don't report an "invalid control flow" error when the provided
  identifier is erroneous. The identifier error is forwarded instead
* introduce the new "label expected" error and use it to replace
  "ill-formed AST" and "invalid control flow" errors when the AST in
  the label slot is not the symbol of a label

Two new error message tests are added for the changed errors.
  • Loading branch information
zerbina authored Aug 22, 2023
1 parent 863664d commit e83f27d
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 25 deletions.
9 changes: 8 additions & 1 deletion compiler/ast/ast_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,9 @@ type
adSemInvalidExpression
adSemExpectedNonemptyPattern
# semstmts
adSemInvalidControlFlow
adSemExpectedLabel
adSemContinueCannotHaveLabel
adSemUseOrDiscardExpr
adSemAmbiguousIdent
adSemCannotConvertToRange # TODO: this should probably mention float?
Expand Down Expand Up @@ -1330,7 +1333,9 @@ type
adSemInvalidRangeConversion,
adSemFoldCannotComputeOffset,
adSemExpectedIdentifierQuoteLimit,
adSemExpectedRangeType:
adSemExpectedRangeType,
adSemExpectedLabel,
adSemContinueCannotHaveLabel:
discard
of adSemExpectedIdentifierInExpr:
notIdent*: PNode
Expand Down Expand Up @@ -1498,6 +1503,8 @@ type
of adSemDefNameSym:
defNameSym*: PSym
defNameSymData*: AdSemDefNameSym
of adSemInvalidControlFlow:
label*: PSym

AdSemDefNameSymKind* = enum
adSemDefNameSymExpectedKindMismatch
Expand Down
1 change: 1 addition & 0 deletions compiler/ast/report_enums.nim
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ type
rsemDiscardingVoid
rsemDiscardingProc
rsemInvalidControlFlow
rsemExpectedLabel
rsemContinueCannotHaveLabel
rsemUseOrDiscard
rsemUseOrDiscardExpr
Expand Down
14 changes: 13 additions & 1 deletion compiler/front/cli_reporter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,9 @@ proc reportBody*(conf: ConfigRef, r: SemReport): string =
if r.sym.isNil: r.ast.render else: r.symstr
]

of rsemExpectedLabel:
result = "expected the name of a label, but got: " & r.ast.render

of rsemContinueCannotHaveLabel:
result = "'continue' cannot have a label"

Expand Down Expand Up @@ -3279,7 +3282,9 @@ func astDiagToLegacyReport(conf: ConfigRef, diag: PAstDiag): Report {.inline.} =
adSemFoldDivByZero,
adSemFoldCannotComputeOffset,
adSemExpectedIdentifierQuoteLimit,
adSemExpectedRangeType:
adSemExpectedRangeType,
adSemExpectedLabel,
adSemContinueCannotHaveLabel:
semRep = SemReport(
location: some diag.location,
reportInst: diag.instLoc.toReportLineInfo,
Expand All @@ -3305,6 +3310,13 @@ func astDiagToLegacyReport(conf: ConfigRef, diag: PAstDiag): Report {.inline.} =
ast: diag.wrongNode,
typeMismatch: @[typeMismatch(diag.wrongNode[0].typ,
diag.wrongNode.typ)])
of adSemInvalidControlFlow:
semRep = SemReport(
location: some diag.location,
reportInst: diag.instLoc.toReportLineInfo,
kind: rsemInvalidControlFlow,
ast: diag.wrongNode,
sym: diag.label)
of adSemUseOrDiscardExpr:
semRep = SemReport(
location: some diag.undiscarded.info, # xxx: location override
Expand Down
3 changes: 3 additions & 0 deletions compiler/front/msgs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,9 @@ func astDiagToLegacyReportKind*(
of adSemImplementationNotAllowed: rsemImplementationNotAllowed
of adSemInvalidExpression: rsemInvalidExpression
of adSemExpectedNonemptyPattern: rsemExpectedNonemptyPattern
of adSemInvalidControlFlow: rsemInvalidControlFlow
of adSemExpectedLabel: rsemExpectedLabel
of adSemContinueCannotHaveLabel: rsemContinueCannotHaveLabel
of adSemUseOrDiscardExpr: rsemUseOrDiscardExpr
of adSemCannotConvertToRange: rsemCannotConvertToRange
of adSemProveInit: rsemProveInit
Expand Down
3 changes: 2 additions & 1 deletion compiler/sem/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3839,7 +3839,8 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
of nkDiscardStmt: result = semDiscard(c, n)
of nkWhileStmt: result = semWhile(c, n, flags)
of nkTryStmt, nkHiddenTryStmt: result = semTry(c, n, flags)
of nkBreakStmt, nkContinueStmt: result = semBreakOrContinue(c, n)
of nkBreakStmt: result = c.config.extract(semBreakStmt(c, n))
of nkContinueStmt: result = semContinueStmt(c, n)
of nkForStmt: result = semFor(c, n, flags)
of nkCaseStmt: result = semCase(c, n, flags)
of nkReturnStmt: result = semReturn(c, n)
Expand Down
53 changes: 31 additions & 22 deletions compiler/sem/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,43 @@ proc semDiscard(c: PContext, n: PNode): PNode =
# tyProc is disallowed to prevent ``discard foo`` to be valid, when ``discard foo()`` is meant.
localReport(c.config, n, reportSem rsemDiscardingProc)

proc semBreakOrContinue(c: PContext, n: PNode): PNode =
result = n
proc semBreakStmt(c: PContext, n: PNode): ElaborateAst =
result.initWith(n)
checkSonsLen(n, 1, c.config)
if n[0].kind != nkEmpty:
if n.kind != nkContinueStmt:
var s: PSym
case n[0].kind:
of nkIdent: s = lookUp(c, n[0])
of nkSym: s = n[0].sym
else:
semReportIllformedAst(c.config, n, {nkIdent, nkSym})

s = getGenSym(c, s)
case n[0].kind
of nkIdent, nkSym:
let s = getGenSym(c, lookUp(c, n[0]))
result[0] = newSymNode(s, n[0].info)

case s.kind
of skError:
result[0] = s.ast
of skLabel:
# make sure the label is okay to use:
if s.kind == skLabel and s.owner.id == c.p.owner.id:
var x = newSymNode(s)
x.info = n.info
incl(s.flags, sfUsed)
n[0] = x
suggestSym(c.graph, x.info, s, c.graph.usageSym)
suggestSym(c.graph, n.info, s, c.graph.usageSym)
else:
localReport(c.config, n.info, reportSym(rsemInvalidControlFlow, s))

# a label not part of the current context
result.diag = PAstDiag(kind: adSemInvalidControlFlow, label: s)
else:
localReport(c.config, n, reportSem rsemContinueCannotHaveLabel)
elif (c.p.nestedLoopCounter <= 0) and
((c.p.nestedBlockCounter <= 0) or n.kind == nkContinueStmt):
result[0] = c.config.newError(result[0]):
PAstDiag(kind: adSemExpectedLabel)
of nkEmpty:
result[0] = n[0]
if c.p.nestedLoopCounter <= 0 and c.p.nestedBlockCounter <= 0:
# nothing to break out of
result.diag = PAstDiag(kind: adSemInvalidControlFlow)
else:
result[0] = c.config.newError(n[0], PAstDiag(kind: adSemExpectedLabel))

localReport(c.config, n, reportSem rsemInvalidControlFlow)
proc semContinueStmt(c: PContext, n: PNode): PNode =
if n[0].kind != nkEmpty:
c.config.newError(n, PAstDiag(kind: adSemContinueCannotHaveLabel))
elif c.p.nestedLoopCounter <= 0:
c.config.newError(n, PAstDiag(kind: adSemInvalidControlFlow))
else:
n

proc semAsm(c: PContext, n: PNode): PNode =
checkSonsLen(n, 2, c.config)
Expand Down
7 changes: 7 additions & 0 deletions tests/errmsgs/texpected_label_1.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
discard """
errormsg: "expected the name of a label, but got: 1 + 1"
line: 7
"""

block:
break 1 + 1
8 changes: 8 additions & 0 deletions tests/errmsgs/texpected_label_2.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
discard """
errormsg: "expected the name of a label, but got: a"
line: 8
"""

block:
var a: int
break a

0 comments on commit e83f27d

Please sign in to comment.