[Sema] Move SingleValueStmtUsageChecker into pre-checking

These diagnostics are better suited for pre-checking
since we ought to be emitting them even if there's
some other error with the expression.
This commit is contained in:
Hamish Knight
2024-08-30 18:55:48 +01:00
parent 1139b0e799
commit 89a3390aa1
12 changed files with 248 additions and 253 deletions

View File

@@ -4378,206 +4378,6 @@ void VarDeclUsageChecker::handleIfConfig(IfConfigDecl *ICD) {
}
}
namespace {
class SingleValueStmtUsageChecker final : public ASTWalker {
ASTContext &Ctx;
DiagnosticEngine &Diags;
llvm::DenseSet<SingleValueStmtExpr *> ValidSingleValueStmtExprs;
public:
SingleValueStmtUsageChecker(
ASTContext &ctx, ASTNode root,
std::optional<ContextualTypePurpose> contextualPurpose)
: Ctx(ctx), Diags(ctx.Diags) {
assert(!root.is<Expr *>() || contextualPurpose &&
"Must provide contextual purpose for expr");
// If we have a contextual purpose, this is for an expression. Check if it's
// an expression in a valid position.
if (contextualPurpose) {
markAnyValidTopLevelSingleValueStmt(root.get<Expr *>(),
*contextualPurpose);
}
}
private:
/// Mark a given expression as a valid position for a SingleValueStmtExpr.
void markValidSingleValueStmt(Expr *E) {
if (!E)
return;
if (auto *SVE = SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(E))
ValidSingleValueStmtExprs.insert(SVE);
}
/// Mark a valid top-level expression with a given contextual purpose.
void markAnyValidTopLevelSingleValueStmt(Expr *E, ContextualTypePurpose ctp) {
// Allowed in returns, throws, and bindings.
switch (ctp) {
case CTP_ReturnStmt:
case CTP_ThrowStmt:
case CTP_Initialization:
markValidSingleValueStmt(E);
break;
default:
break;
}
}
MacroWalking getMacroWalkingBehavior() const override {
return MacroWalking::ArgumentsAndExpansion;
}
AssignExpr *findAssignment(Expr *E) const {
// Don't consider assignments if we have a parent expression (as otherwise
// this would be effectively allowing it in an arbitrary expression
// position).
if (Parent.getAsExpr())
return nullptr;
// Look through optional exprs, which are present for e.g x?.y = z, as
// we wrap the entire assign in the optional evaluation of the destination.
if (auto *OEE = dyn_cast<OptionalEvaluationExpr>(E)) {
E = OEE->getSubExpr();
while (auto *IIO = dyn_cast<InjectIntoOptionalExpr>(E))
E = IIO->getSubExpr();
}
return dyn_cast<AssignExpr>(E);
}
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
if (auto *SVE = dyn_cast<SingleValueStmtExpr>(E)) {
// Diagnose a SingleValueStmtExpr in a context that we do not currently
// support. If we start allowing these in arbitrary places, we'll need
// to ensure that autoclosures correctly contextualize them.
if (!ValidSingleValueStmtExprs.contains(SVE)) {
Diags.diagnose(SVE->getLoc(), diag::single_value_stmt_out_of_place,
SVE->getStmt()->getKind());
}
// Diagnose invalid SingleValueStmtExprs. This should only happen for
// expressions in positions that we didn't support before
// (e.g assignment or *explicit* return).
auto *S = SVE->getStmt();
auto mayProduceSingleValue = S->mayProduceSingleValue(Ctx);
switch (mayProduceSingleValue.getKind()) {
case IsSingleValueStmtResult::Kind::Valid:
break;
case IsSingleValueStmtResult::Kind::UnterminatedBranches: {
for (auto *branch : mayProduceSingleValue.getUnterminatedBranches()) {
if (auto *BS = dyn_cast<BraceStmt>(branch)) {
if (BS->empty()) {
Diags.diagnose(branch->getStartLoc(),
diag::single_value_stmt_branch_empty,
S->getKind());
continue;
}
}
// TODO: The wording of this diagnostic will need tweaking if either
// implicit last expressions or 'then' statements are enabled by
// default.
Diags.diagnose(branch->getEndLoc(),
diag::single_value_stmt_branch_must_end_in_result,
S->getKind(), isa<SwitchStmt>(S));
}
break;
}
case IsSingleValueStmtResult::Kind::NonExhaustiveIf: {
Diags.diagnose(S->getStartLoc(),
diag::if_expr_must_be_syntactically_exhaustive);
break;
}
case IsSingleValueStmtResult::Kind::NonExhaustiveDoCatch: {
Diags.diagnose(S->getStartLoc(),
diag::do_catch_expr_must_be_syntactically_exhaustive);
break;
}
case IsSingleValueStmtResult::Kind::HasLabel: {
// FIXME: We should offer a fix-it to remove (currently we don't track
// the colon SourceLoc).
auto label = cast<LabeledStmt>(S)->getLabelInfo();
Diags.diagnose(label.Loc,
diag::single_value_stmt_must_be_unlabeled, S->getKind())
.highlight(label.Loc);
break;
}
case IsSingleValueStmtResult::Kind::InvalidJumps: {
// Diagnose each invalid jump.
for (auto *jump : mayProduceSingleValue.getInvalidJumps()) {
Diags.diagnose(jump->getStartLoc(),
diag::cannot_jump_in_single_value_stmt,
jump->getKind(), S->getKind())
.highlight(jump->getSourceRange());
}
break;
}
case IsSingleValueStmtResult::Kind::NoResult:
// This is fine, we will have typed the expression as Void (we verify
// as such in the ASTVerifier).
break;
case IsSingleValueStmtResult::Kind::CircularReference:
// Already diagnosed.
break;
case IsSingleValueStmtResult::Kind::UnhandledStmt:
break;
}
return Action::Continue(E);
}
// Valid as the source of an assignment.
if (auto *AE = findAssignment(E))
markValidSingleValueStmt(AE->getSrc());
// Valid as a single expression body of a closure. This is needed in
// addition to ReturnStmt checking, as we will remove the return if the
// expression is inferred to be Never.
if (auto *ACE = dyn_cast<ClosureExpr>(E)) {
if (ACE->hasSingleExpressionBody())
markValidSingleValueStmt(ACE->getSingleExpressionBody());
}
return Action::Continue(E);
}
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
// Valid in a return/throw/then.
if (auto *RS = dyn_cast<ReturnStmt>(S)) {
if (RS->hasResult())
markValidSingleValueStmt(RS->getResult());
}
if (auto *TS = dyn_cast<ThrowStmt>(S))
markValidSingleValueStmt(TS->getSubExpr());
if (auto *TS = dyn_cast<ThenStmt>(S))
markValidSingleValueStmt(TS->getResult());
return Action::Continue(S);
}
PreWalkAction walkToDeclPre(Decl *D) override {
// Valid as an initializer of a pattern binding.
if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
for (auto idx : range(PBD->getNumPatternEntries()))
markValidSingleValueStmt(PBD->getInit(idx));
return Action::Continue();
}
// We don't want to walk into any other decl, we will visit them as part of
// typeCheckDecl.
return Action::SkipNode();
}
};
} // end anonymous namespace
void swift::diagnoseOutOfPlaceExprs(
ASTContext &ctx, ASTNode root,
std::optional<ContextualTypePurpose> contextualPurpose) {
// TODO: We ought to consider moving this into pre-checking such that we can
// still diagnose on invalid code, and don't have to traverse over implicit
// exprs. We need to first separate out SequenceExpr folding though.
SingleValueStmtUsageChecker sveChecker(ctx, root, contextualPurpose);
root.walk(sveChecker);
}
/// Apply the warnings managed by VarDeclUsageChecker to the top level
/// code declarations that haven't been checked yet.
void swift::
@@ -6426,10 +6226,9 @@ diagnoseDictionaryLiteralDuplicateKeyEntries(const Expr *E,
//===----------------------------------------------------------------------===//
/// Emit diagnostics for syntactic restrictions on a given expression.
void swift::performSyntacticExprDiagnostics(
const Expr *E, const DeclContext *DC,
std::optional<ContextualTypePurpose> contextualPurpose, bool isExprStmt,
bool disableOutOfPlaceExprChecking) {
void swift::performSyntacticExprDiagnostics(const Expr *E,
const DeclContext *DC,
bool isExprStmt) {
auto &ctx = DC->getASTContext();
TypeChecker::diagnoseSelfAssignment(E);
diagSyntacticUseRestrictions(E, DC, isExprStmt);
@@ -6448,8 +6247,6 @@ void swift::performSyntacticExprDiagnostics(
diagnoseConstantArgumentRequirement(E, DC);
diagUnqualifiedAccessToMethodNamedSelf(E, DC);
diagnoseDictionaryLiteralDuplicateKeyEntries(E, DC);
if (!disableOutOfPlaceExprChecking)
diagnoseOutOfPlaceExprs(ctx, const_cast<Expr *>(E), contextualPurpose);
}
void swift::performStmtDiagnostics(const Stmt *S, DeclContext *DC) {

View File

@@ -38,21 +38,9 @@ namespace swift {
class ValueDecl;
class ForEachStmt;
/// Diagnose any expressions that appear in an unsupported position. If visiting
/// an expression directly, its \p contextualPurpose should be provided to
/// evaluate its position.
void diagnoseOutOfPlaceExprs(
ASTContext &ctx, ASTNode root,
std::optional<ContextualTypePurpose> contextualPurpose);
/// Emit diagnostics for syntactic restrictions on a given expression.
///
/// Note: \p contextualPurpose must be non-nil, unless
/// \p disableOutOfPlaceExprChecking is set to \c true.
void performSyntacticExprDiagnostics(
const Expr *E, const DeclContext *DC,
std::optional<ContextualTypePurpose> contextualPurpose, bool isExprStmt,
bool disableOutOfPlaceExprChecking = false);
void performSyntacticExprDiagnostics(const Expr *E, const DeclContext *DC,
bool isExprStmt);
/// Emit diagnostics for a given statement.
void performStmtDiagnostics(const Stmt *S, DeclContext *DC);

View File

@@ -1054,6 +1054,11 @@ class PreCheckTarget final : public ASTWalker {
/// Keep track of acceptable DiscardAssignmentExpr's.
llvm::SmallPtrSet<DiscardAssignmentExpr *, 2> CorrectDiscardAssignmentExprs;
/// Keep track of any out-of-place SingleValueStmtExprs. We populate this as
/// we encounter SingleValueStmtExprs, and erase them as we walk up to a
/// valid parent in the post walk.
llvm::SetVector<SingleValueStmtExpr *> OutOfPlaceSingleValueStmtExprs;
/// Simplify expressions which are type sugar productions that got parsed
/// as expressions due to the parser not knowing which identifiers are
/// type names.
@@ -1100,9 +1105,36 @@ class PreCheckTarget final : public ASTWalker {
/// in simple pattern-like expressions, so we reject anything complex here.
void markAcceptableDiscardExprs(Expr *E);
public:
/// Check and diagnose an invalid SingleValueStmtExpr.
void checkSingleValueStmtExpr(SingleValueStmtExpr *SVE);
/// Diagnose any SingleValueStmtExprs in an unsupported position.
void
diagnoseOutOfPlaceSingleValueStmtExprs(const SyntacticElementTarget &target);
/// Mark a given expression as a valid position for a SingleValueStmtExpr.
void markValidSingleValueStmt(Expr *E);
/// For the given expr, mark any valid SingleValueStmtExpr children.
void markAnyValidSingleValueStmts(Expr *E);
/// For the given statement, mark any valid SingleValueStmtExpr children.
void markAnyValidSingleValueStmts(Stmt *S);
PreCheckTarget(DeclContext *dc) : Ctx(dc->getASTContext()), DC(dc) {}
public:
static std::optional<SyntacticElementTarget>
check(const SyntacticElementTarget &target) {
PreCheckTarget checker(target.getDeclContext());
auto newTarget = target.walk(checker);
if (!newTarget)
return std::nullopt;
// Diagnose any remaining out-of-place SingleValueStmtExprs.
checker.diagnoseOutOfPlaceSingleValueStmtExprs(*newTarget);
return *newTarget;
}
ASTContext &getASTContext() const { return Ctx; }
bool walkToClosureExprPre(ClosureExpr *expr);
@@ -1275,6 +1307,9 @@ public:
if (auto *assignment = dyn_cast<AssignExpr>(expr))
markAcceptableDiscardExprs(assignment->getDest());
if (auto *SVE = dyn_cast<SingleValueStmtExpr>(expr))
checkSingleValueStmtExpr(SVE);
return finish(true, expr);
}
@@ -1283,6 +1318,9 @@ public:
assert(ExprStack.back() == expr);
ExprStack.pop_back();
// Mark any valid SingleValueStmtExpr children.
markAnyValidSingleValueStmts(expr);
// Type check the type parameters in an UnresolvedSpecializeExpr.
if (auto *us = dyn_cast<UnresolvedSpecializeExpr>(expr)) {
if (auto *typeExpr = simplifyUnresolvedSpecializeExpr(us))
@@ -1451,8 +1489,22 @@ public:
return Action::Continue(stmt);
}
PostWalkResult<Stmt *> walkToStmtPost(Stmt *S) override {
markAnyValidSingleValueStmts(S);
return Action::Continue(S);
}
PreWalkAction walkToDeclPre(Decl *D) override {
return Action::VisitNodeIf(isa<PatternBindingDecl>(D));
return Action::VisitChildrenIf(isa<PatternBindingDecl>(D));
}
PostWalkAction walkToDeclPost(Decl *D) override {
// Mark any valid SingleValueStmtExprs for initializations.
if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
for (auto idx : range(PBD->getNumPatternEntries()))
markValidSingleValueStmt(PBD->getInit(idx));
}
return Action::Continue();
}
PreWalkResult<Pattern *> walkToPatternPre(Pattern *pattern) override {
@@ -1487,6 +1539,145 @@ bool PreCheckTarget::walkToClosureExprPre(ClosureExpr *closure) {
return true;
}
void PreCheckTarget::markValidSingleValueStmt(Expr *E) {
if (!E)
return;
if (auto *SVE = SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(E))
OutOfPlaceSingleValueStmtExprs.remove(SVE);
}
void PreCheckTarget::checkSingleValueStmtExpr(SingleValueStmtExpr *SVE) {
// We add all SingleValueStmtExprs we see to the out-of-place list, then
// erase them as we walk up to valid parents. We do this instead of populating
// valid positions during the pre-walk to ensure we're looking at the AST
// after e.g folding SequenceExprs.
OutOfPlaceSingleValueStmtExprs.insert(SVE);
// Diagnose invalid SingleValueStmtExprs. This should only happen for
// expressions in positions that we didn't support prior to their introduction
// (e.g assignment or *explicit* return).
auto &Diags = Ctx.Diags;
auto *S = SVE->getStmt();
auto mayProduceSingleValue = S->mayProduceSingleValue(Ctx);
switch (mayProduceSingleValue.getKind()) {
case IsSingleValueStmtResult::Kind::Valid:
break;
case IsSingleValueStmtResult::Kind::UnterminatedBranches: {
for (auto *branch : mayProduceSingleValue.getUnterminatedBranches()) {
if (auto *BS = dyn_cast<BraceStmt>(branch)) {
if (BS->empty()) {
Diags.diagnose(branch->getStartLoc(),
diag::single_value_stmt_branch_empty, S->getKind());
continue;
}
}
// TODO: The wording of this diagnostic will need tweaking if either
// implicit last expressions or 'then' statements are enabled by
// default.
Diags.diagnose(branch->getEndLoc(),
diag::single_value_stmt_branch_must_end_in_result,
S->getKind(), isa<SwitchStmt>(S));
}
break;
}
case IsSingleValueStmtResult::Kind::NonExhaustiveIf: {
Diags.diagnose(S->getStartLoc(),
diag::if_expr_must_be_syntactically_exhaustive);
break;
}
case IsSingleValueStmtResult::Kind::NonExhaustiveDoCatch: {
Diags.diagnose(S->getStartLoc(),
diag::do_catch_expr_must_be_syntactically_exhaustive);
break;
}
case IsSingleValueStmtResult::Kind::HasLabel: {
// FIXME: We should offer a fix-it to remove (currently we don't track
// the colon SourceLoc).
auto label = cast<LabeledStmt>(S)->getLabelInfo();
Diags.diagnose(label.Loc,
diag::single_value_stmt_must_be_unlabeled, S->getKind())
.highlight(label.Loc);
break;
}
case IsSingleValueStmtResult::Kind::InvalidJumps: {
// Diagnose each invalid jump.
for (auto *jump : mayProduceSingleValue.getInvalidJumps()) {
Diags.diagnose(jump->getStartLoc(),
diag::cannot_jump_in_single_value_stmt,
jump->getKind(), S->getKind())
.highlight(jump->getSourceRange());
}
break;
}
case IsSingleValueStmtResult::Kind::NoResult:
// This is fine, we will have typed the expression as Void (we verify
// as such in the ASTVerifier).
break;
case IsSingleValueStmtResult::Kind::CircularReference:
// Already diagnosed.
break;
case IsSingleValueStmtResult::Kind::UnhandledStmt:
break;
}
}
void PreCheckTarget::markAnyValidSingleValueStmts(Expr *E) {
auto findAssignment = [&]() -> AssignExpr * {
// Don't consider assignments if we have a parent expression (as otherwise
// this would be effectively allowing it in an arbitrary expression
// position).
if (Parent.getAsExpr())
return nullptr;
// Look through optional exprs, which are present for e.g x?.y = z, as
// we wrap the entire assign in the optional evaluation of the destination.
if (auto *OEE = dyn_cast<OptionalEvaluationExpr>(E)) {
E = OEE->getSubExpr();
while (auto *IIO = dyn_cast<InjectIntoOptionalExpr>(E))
E = IIO->getSubExpr();
}
return dyn_cast<AssignExpr>(E);
};
if (auto *AE = findAssignment())
markValidSingleValueStmt(AE->getSrc());
}
void PreCheckTarget::markAnyValidSingleValueStmts(Stmt *S) {
// Valid in a return/throw/then.
if (auto *RS = dyn_cast<ReturnStmt>(S)) {
if (RS->hasResult())
markValidSingleValueStmt(RS->getResult());
}
if (auto *TS = dyn_cast<ThrowStmt>(S))
markValidSingleValueStmt(TS->getSubExpr());
if (auto *TS = dyn_cast<ThenStmt>(S))
markValidSingleValueStmt(TS->getResult());
}
void PreCheckTarget::diagnoseOutOfPlaceSingleValueStmtExprs(
const SyntacticElementTarget &target) {
// Top-level SingleValueStmtExprs are allowed in returns, throws, and
// bindings.
if (auto *E = target.getAsExpr()) {
switch (target.getExprContextualTypePurpose()) {
case CTP_ReturnStmt:
case CTP_ThrowStmt:
case CTP_Initialization:
markValidSingleValueStmt(E);
break;
default:
break;
}
}
for (auto *SVE : OutOfPlaceSingleValueStmtExprs) {
Ctx.Diags.diagnose(SVE->getLoc(), diag::single_value_stmt_out_of_place,
SVE->getStmt()->getKind());
}
}
TypeExpr *PreCheckTarget::simplifyNestedTypeExpr(UnresolvedDotExpr *UDE) {
if (!UDE->getName().isSimpleName() ||
UDE->getName().isSpecial())
@@ -2427,9 +2618,7 @@ bool ConstraintSystem::preCheckTarget(SyntacticElementTarget &target) {
auto &ctx = DC->getASTContext();
FrontendStatsTracer StatsTracer(ctx.Stats, "precheck-target");
PreCheckTarget preCheck(DC);
auto newTarget = target.walk(preCheck);
auto newTarget = PreCheckTarget::check(target);
if (!newTarget)
return true;

View File

@@ -340,10 +340,7 @@ public:
}
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
// We skip out-of-place expr checking here since we've already performed it.
performSyntacticExprDiagnostics(expr, dcStack.back(), /*ctp*/ std::nullopt,
/*isExprStmt=*/false,
/*disableOutOfPlaceExprChecking*/ true);
performSyntacticExprDiagnostics(expr, dcStack.back(), /*isExprStmt=*/false);
if (auto closure = dyn_cast<ClosureExpr>(expr)) {
if (closure->isSeparatelyTypeChecked()) {
@@ -389,9 +386,7 @@ void constraints::performSyntacticDiagnosticsForTarget(
switch (target.kind) {
case SyntacticElementTarget::Kind::expression: {
// First emit diagnostics for the main expression.
performSyntacticExprDiagnostics(target.getAsExpr(), dc,
target.getExprContextualTypePurpose(),
isExprStmt);
performSyntacticExprDiagnostics(target.getAsExpr(), dc, isExprStmt);
return;
}
@@ -400,22 +395,15 @@ void constraints::performSyntacticDiagnosticsForTarget(
// First emit diagnostics for the main expression.
performSyntacticExprDiagnostics(stmt->getTypeCheckedSequence(), dc,
CTP_ForEachSequence, isExprStmt);
isExprStmt);
if (auto *whereExpr = stmt->getWhere())
performSyntacticExprDiagnostics(whereExpr, dc, CTP_Condition,
/*isExprStmt*/ false);
performSyntacticExprDiagnostics(whereExpr, dc, /*isExprStmt*/ false);
return;
}
case SyntacticElementTarget::Kind::function: {
// Check for out of place expressions. This needs to be done on the entire
// function body rather than on individual expressions since we need the
// context of the parent nodes.
auto *body = target.getFunctionBody();
diagnoseOutOfPlaceExprs(dc->getASTContext(), body,
/*contextualPurpose*/ std::nullopt);
FunctionSyntacticDiagnosticWalker walker(dc);
body->walk(walker);
return;

View File

@@ -1158,6 +1158,8 @@ func rdar77022842(argA: Bool? = nil, argB: Bool? = nil) {
// expected-error@-1 {{initializer for conditional binding must have Optional type, not 'Bool'}}
// expected-error@-2 {{closure passed to parameter of type 'Bool?' that does not accept a closure}}
// expected-error@-3 {{cannot convert value of type 'Void' to expected condition type 'Bool'}}
// expected-error@-4 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
// expected-error@-5 {{'if' must have an unconditional 'else' to be used as expression}}
} // expected-error {{expected '{' after 'if' condition}}
}

View File

@@ -384,8 +384,10 @@ func testVoidConversion() {
func testReturnMismatch() {
let _ = if .random() {
return 1 // expected-error {{unexpected non-void return value in void function}}
// expected-note@-1 {{did you mean to add a return type?}}
return 1
// expected-error@-1 {{cannot use 'return' to transfer control out of 'if' expression}}
// expected-error@-2 {{unexpected non-void return value in void function}}
// expected-note@-3 {{did you mean to add a return type?}}
} else {
0
}
@@ -433,7 +435,9 @@ func testAssignment() {
}
struct TestBadReturn {
var y = if .random() { return } else { 0 } // expected-error {{return invalid outside of a func}}
var y = if .random() { return } else { 0 }
// expected-error@-1 {{return invalid outside of a func}}
// expected-error@-2 {{cannot use 'return' to transfer control out of 'if' expression}}
}
struct SomeError: Error {}

View File

@@ -23,10 +23,10 @@ func baz() -> String? {
var x: Int? = {
print( // expected-error {{cannot convert value of type '()' to closure result type 'Int?'}}
// expected-note@-1 {{to match this opening '('}}
switch baz() {
switch baz() { // expected-error {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
case ""?:
return nil
return nil // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
default:
return nil
return nil // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
}
}() // expected-error {{expected ')' in expression list}}

View File

@@ -191,7 +191,9 @@ func testAssignment() {
}
struct TestBadReturn {
var y = switch Bool.random() { case true: return case false: 0 } // expected-error {{return invalid outside of a func}}
var y = switch Bool.random() { case true: return case false: 0 }
// expected-error@-1 {{return invalid outside of a func}}
// expected-error@-2 {{cannot use 'return' to transfer control out of 'switch' expression}}
}
func testNil1(_ x: Bool) {

View File

@@ -82,6 +82,8 @@ func missingControllingExprInIf() {
if { // expected-error {{missing condition in 'if' statement}}
} // expected-error {{expected '{' after 'if' condition}}
// expected-error@-2 {{cannot convert value of type 'Void' to expected condition type 'Bool'}}
// expected-error@-3 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
// expected-error@-4 {{'if' must have an unconditional 'else' to be used as expression}}
if // expected-error {{missing condition in 'if' statement}}
{
@@ -239,6 +241,7 @@ func missingControllingExprInSwitch() {
switch { // expected-error {{expected expression in 'switch' statement}}
} // expected-error {{expected '{' after 'switch' subject expression}}
// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
switch // expected-error {{expected expression in 'switch' statement}} expected-error {{'switch' statement body must have at least one 'case' or 'default' block}}
{

View File

@@ -116,12 +116,14 @@ takesValue(if .random() { 0 } else { 1 })
do {
takesValue(x: if .random() { 0 } else { 1 })
// expected-error@-1 {{extraneous argument label 'x:' in call}}
// expected-error@-2 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
takesValue(_: x: if .random() { 0 } else { 1 })
// expected-error@-1 {{expected argument label before colon}}
// expected-error@-2 {{expected ',' separator}}
// expected-error@-3 {{cannot find 'x' in scope}}
// expected-error@-4 {{extra argument in call}}
// expected-error@-5 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
}
func takesValueWithLabel<T>(x: T) {}
do {
@@ -133,6 +135,7 @@ do {
// expected-error@-2 {{expected ',' separator}}
// expected-error@-3 {{cannot find 'y' in scope}}
// expected-error@-4 {{extra argument in call}}
// expected-error@-5 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
}
func takesValueAndTrailingClosure<T>(_ x: T, _ fn: () -> Int) {}
takesValueAndTrailingClosure(if .random() { 0 } else { 1 }) { 2 }
@@ -141,6 +144,7 @@ takesValueAndTrailingClosure(if .random() { 0 } else { 1 }) { 2 }
func takesInOut<T>(_ x: inout T) {}
takesInOut(&if .random() { 1 } else { 2 })
// expected-error@-1 {{cannot pass immutable value of type 'Int' as inout argument}}
// expected-error@-2 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
struct HasSubscript {
static subscript(x: Int) -> Void { () }
@@ -462,6 +466,9 @@ let o = !if .random() { true } else { false } // expected-error {{'if' may only
let p = if .random() { 1 } else { 2 } + // expected-error {{ambiguous use of operator '+'}}
if .random() { 3 } else { 4 } +
if .random() { 5 } else { 6 }
// expected-error@-3 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
// expected-error@-3 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
// expected-error@-3 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
let p1 = if .random() { 1 } else { 2 } + 5
// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
@@ -504,6 +511,7 @@ do {
// FIXME: The type error is likely due to not solving the conjunction before attempting default type var bindings.
let _ = (if .random() { Int?.none } else { 1 as Int? })?.bitWidth
// expected-error@-1 {{type of expression is ambiguous without a type annotation}}
// expected-error@-2 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
}
do {
let _ = if .random() { Int?.none } else { 1 as Int? }!
@@ -578,6 +586,8 @@ func stmts() {
for _ in if .random() { [true] } else { [false] } {}
// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
for _ in if .random() { [true] } else { [false] } {} // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
// Make sure this doesn't parse as an if expr pattern with a label.
let x = 0
switch 0 {
@@ -609,9 +619,9 @@ func returnBranches() -> Int {
func returnBranches1() -> Int {
return if .random() { // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}}
return 0
return 0 // expected-error {{cannot use 'return' to transfer control out of 'if' expression}}
} else {
return 1
return 1 // expected-error {{cannot use 'return' to transfer control out of 'if' expression}}
}
}

View File

@@ -163,12 +163,14 @@ takesValue(switch Bool.random() { case true: 1 case false: 2 })
do {
takesValue(x: switch Bool.random() { case true: 1 case false: 2 })
// expected-error@-1 {{extraneous argument label 'x:' in call}}
// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
takesValue(_: x: switch Bool.random() { case true: 1 case false: 2 })
// expected-error@-1 {{expected argument label before colon}}
// expected-error@-2 {{expected ',' separator}}
// expected-error@-3 {{cannot find 'x' in scope}}
// expected-error@-4 {{extra argument in call}}
// expected-error@-5 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
}
func takesValueWithLabel<T>(x: T) {}
do {
@@ -180,6 +182,7 @@ do {
// expected-error@-2 {{expected ',' separator}}
// expected-error@-3 {{cannot find 'y' in scope}}
// expected-error@-4 {{extra argument in call}}
// expected-error@-5 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
}
func takesValueAndTrailingClosure<T>(_ x: T, _ fn: () -> Int) {}
takesValueAndTrailingClosure(switch Bool.random() { case true: 0 case false: 1 }) { 2 }
@@ -188,6 +191,7 @@ takesValueAndTrailingClosure(switch Bool.random() { case true: 0 case false: 1 }
func takesInOut<T>(_ x: inout T) {}
takesInOut(&switch Bool.random() { case true: 1 case false: 2 })
// expected-error@-1 {{cannot pass immutable value of type 'Int' as inout argument}}
// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
struct HasSubscript {
static subscript(x: Int) -> Void { () }
@@ -496,9 +500,11 @@ do {
do {
_ = (switch fatalError() {}, 1) // expected-error {{expected '{' after 'switch' subject expression}}
// expected-error@-1 {{extra trailing closure passed in call}}
// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
_ = (switch fatalError() { #if FOO
// expected-error@-1 {{extra trailing closure passed in call}}
// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
#endif
}, 0) // expected-error {{expected '{' after 'switch' subject expression}}
@@ -507,6 +513,7 @@ do {
// expected-error@-2 {{type '() -> ()' cannot conform to 'RandomNumberGenerator'}}
// expected-note@-3 {{required by static method 'random(using:)' where 'T' = '() -> ()'}}
// expected-note@-4 {{only concrete types such as structs, enums and classes can conform to protocols}}
// expected-error@-5 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
case true: // expected-error {{'case' label can only appear inside a 'switch' statement}}
1
case false: // expected-error {{'case' label can only appear inside a 'switch' statement}}
@@ -519,6 +526,7 @@ do {
// expected-error@-2 {{type '() -> ()' cannot conform to 'RandomNumberGenerator'}}
// expected-note@-3 {{required by static method 'random(using:)' where 'T' = '() -> ()'}}
// expected-note@-4 {{only concrete types such as structs, enums and classes can conform to protocols}}
// expected-error@-5 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
case true: // expected-error {{'case' label can only appear inside a 'switch' statement}}
1
case false: // expected-error {{'case' label can only appear inside a 'switch' statement}}
@@ -618,6 +626,9 @@ let m = !switch Bool.random() { case true: true case false: true }
let n = switch Bool.random() { case true: 1 case false: 2 } + // expected-error {{ambiguous use of operator '+'}}
switch Bool.random() { case true: 3 case false: 4 } +
switch Bool.random() { case true: 5 case false: 6 }
// expected-error@-3 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
// expected-error@-3 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
// expected-error@-3 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
let n1 = switch Bool.random() { case true: 1 case false: 2 } + 5
// expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
@@ -660,6 +671,7 @@ do {
// FIXME: The type error is likely due to not solving the conjunction before attempting default type var bindings.
let _ = (switch Bool.random() { case true: Int?.none case false: 1 })?.bitWidth
// expected-error@-1 {{type of expression is ambiguous without a type annotation}}
// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
}
do {
let _ = switch Bool.random() { case true: Int?.none case false: 1 }!
@@ -749,9 +761,9 @@ func returnBranches() -> Int {
func returnBranches1() -> Int {
return switch Bool.random() { // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}}
case true:
return 0
return 0 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
case false:
return 1
return 1 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
}
}

View File

@@ -6,6 +6,6 @@ let x = if .random() {
then 0
// expected-error@-1 {{cannot find 'then' in scope}}
// expected-error@-2 {{consecutive statements on a line must be separated by ';'}}
} else {
} else { // expected-error {{non-expression branch of 'if' expression may only end with a 'throw'}}
1
}