From 5d478bdb3b7638f5df6f0e1f4e574bececae9b80 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 9 May 2017 21:07:37 +0900 Subject: [PATCH 1/2] [Parse] Allow #if to guard switch case clauses Resolves: https://bugs.swift.org/browse/SR-4196 https://bugs.swift.org/browse/SR-2 --- include/swift/AST/Stmt.h | 26 ++- include/swift/Parse/Parser.h | 5 +- lib/AST/ASTDumper.cpp | 7 +- lib/AST/ASTPrinter.cpp | 7 +- lib/AST/ASTWalker.cpp | 19 +- lib/AST/Stmt.cpp | 13 +- lib/Parse/ParseStmt.cpp | 112 ++++++--- lib/SILGen/SILGenPattern.cpp | 2 +- lib/Sema/DerivedConformanceCodingKey.cpp | 4 +- .../DerivedConformanceEquatableHashable.cpp | 2 +- .../DerivedConformanceRawRepresentable.cpp | 4 +- lib/Sema/TypeCheckStmt.cpp | 7 +- lib/Sema/TypeCheckSwitchStmt.cpp | 3 +- .../ConditionalCompilation/switch_case.swift | 221 ++++++++++++++++++ .../switch_case_executable.swift | 107 +++++++++ test/Parse/invalid.swift | 11 - 16 files changed, 474 insertions(+), 76 deletions(-) create mode 100644 test/Parse/ConditionalCompilation/switch_case.swift create mode 100644 test/Parse/ConditionalCompilation/switch_case_executable.swift diff --git a/include/swift/AST/Stmt.h b/include/swift/AST/Stmt.h index afb0d457228b7..9b090702ae2c6 100644 --- a/include/swift/AST/Stmt.h +++ b/include/swift/AST/Stmt.h @@ -932,7 +932,7 @@ class CaseStmt final : public Stmt, /// Switch statement. class SwitchStmt final : public LabeledStmt, - private llvm::TrailingObjects { + private llvm::TrailingObjects { friend TrailingObjects; SourceLoc SwitchLoc, LBraceLoc, RBraceLoc; @@ -953,7 +953,7 @@ class SwitchStmt final : public LabeledStmt, static SwitchStmt *create(LabeledStmtInfo LabelInfo, SourceLoc SwitchLoc, Expr *SubjectExpr, SourceLoc LBraceLoc, - ArrayRef Cases, + ArrayRef Cases, SourceLoc RBraceLoc, ASTContext &C); @@ -972,10 +972,28 @@ class SwitchStmt final : public LabeledStmt, /// Get the subject expression of the switch. Expr *getSubjectExpr() const { return SubjectExpr; } void setSubjectExpr(Expr *e) { SubjectExpr = e; } + + ArrayRef getRawCases() const { + return {getTrailingObjects(), CaseCount}; + } + +private: + struct AsCaseStmtWithSkippingIfConfig { + AsCaseStmtWithSkippingIfConfig() {} + Optional operator()(const ASTNode &N) const { + if (auto *CS = llvm::dyn_cast_or_null(N.dyn_cast())) + return CS; + return None; + } + }; + +public: + using AsCaseStmtRange = OptionalTransformRange, + AsCaseStmtWithSkippingIfConfig>; /// Get the list of case clauses. - ArrayRef getCases() const { - return {getTrailingObjects(), CaseCount}; + AsCaseStmtRange getCases() const { + return AsCaseStmtRange(getRawCases(), AsCaseStmtWithSkippingIfConfig()); } static bool classof(const Stmt *S) { diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index b83a3a6bcd1ad..2d7a40cb912d4 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -1269,6 +1269,8 @@ class Parser { // Statement Parsing bool isStartOfStmt(); + bool isTerminatorForBraceItemListKind(BraceItemListKind Kind, + ArrayRef ParsedDecls); ParserResult parseStmt(); ParserStatus parseExprOrStmt(ASTNode &Result); ParserResult parseStmtBreak(); @@ -1291,7 +1293,8 @@ class Parser { ParserResult parseStmtForEach(SourceLoc ForLoc, LabeledStmtInfo LabelInfo); ParserResult parseStmtSwitch(LabeledStmtInfo LabelInfo); - ParserResult parseStmtCase(); + ParserStatus parseStmtCases(SmallVectorImpl &cases, bool IsActive); + ParserResult parseStmtCase(bool IsActive); //===--------------------------------------------------------------------===// // Generics Parsing diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 67f9c7a3795ce..a0129efb0887b 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -1522,9 +1522,12 @@ class PrintStmt : public StmtVisitor { void visitSwitchStmt(SwitchStmt *S) { printCommon(S, "switch_stmt") << '\n'; printRec(S->getSubjectExpr()); - for (CaseStmt *C : S->getCases()) { + for (auto N : S->getRawCases()) { OS << '\n'; - printRec(C); + if (N.is()) + printRec(N.get()); + else + printRec(N.get()); } PrintWithColorRAII(OS, ParenthesisColor) << ')'; } diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 4084086559a60..440a51a221832 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -3359,8 +3359,11 @@ void PrintAST::visitSwitchStmt(SwitchStmt *stmt) { // FIXME: print subject Printer << "{"; Printer.printNewline(); - for (CaseStmt *C : stmt->getCases()) { - visit(C); + for (auto N : stmt->getRawCases()) { + if (N.is()) + visit(cast(N.get())); + else + visit(cast(N.get())); } Printer.printNewline(); indent(); diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index f190b4ce96fc0..1f225b1adf034 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -1452,12 +1452,19 @@ Stmt *Traversal::visitSwitchStmt(SwitchStmt *S) { else return nullptr; - for (CaseStmt *aCase : S->getCases()) { - if (Stmt *aStmt = doIt(aCase)) { - assert(aCase == aStmt && "switch case remap not supported"); - (void)aStmt; - } else - return nullptr; + for (auto N : S->getRawCases()) { + if (Stmt *aCase = N.dyn_cast()) { + assert(isa(aCase)); + if (Stmt *aStmt = doIt(aCase)) { + assert(aCase == aStmt && "switch case remap not supported"); + (void)aStmt; + } else + return nullptr; + } else { + assert(isa(N.get())); + if (doIt(N.get())) + return nullptr; + } } return S; diff --git a/lib/AST/Stmt.cpp b/lib/AST/Stmt.cpp index 2b7d11f5a8e15..18ce6a4220df0 100644 --- a/lib/AST/Stmt.cpp +++ b/lib/AST/Stmt.cpp @@ -412,15 +412,22 @@ CaseStmt *CaseStmt::create(ASTContext &C, SourceLoc CaseLoc, SwitchStmt *SwitchStmt::create(LabeledStmtInfo LabelInfo, SourceLoc SwitchLoc, Expr *SubjectExpr, SourceLoc LBraceLoc, - ArrayRef Cases, + ArrayRef Cases, SourceLoc RBraceLoc, ASTContext &C) { - void *p = C.Allocate(totalSizeToAlloc(Cases.size()), +#ifndef NDEBUG + for (auto N : Cases) + assert((N.is() && isa(N.get())) || + (N.is() && isa(N.get()))); +#endif + + void *p = C.Allocate(totalSizeToAlloc(Cases.size()), alignof(SwitchStmt)); SwitchStmt *theSwitch = ::new (p) SwitchStmt(LabelInfo, SwitchLoc, SubjectExpr, LBraceLoc, Cases.size(), RBraceLoc); + std::uninitialized_copy(Cases.begin(), Cases.end(), - theSwitch->getTrailingObjects()); + theSwitch->getTrailingObjects()); return theSwitch; } diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index 1591ef3678f95..83e210af92f4d 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -130,14 +130,25 @@ ParserStatus Parser::parseExprOrStmt(ASTNode &Result) { return ResultExpr; } -static bool isTerminatorForBraceItemListKind(const Token &Tok, - BraceItemListKind Kind, - ArrayRef ParsedDecls) { +bool Parser::isTerminatorForBraceItemListKind(BraceItemListKind Kind, + ArrayRef ParsedDecls) { switch (Kind) { case BraceItemListKind::Brace: return false; case BraceItemListKind::Case: - return Tok.is(tok::kw_case) || Tok.is(tok::kw_default); + if (Tok.is(tok::pound_if)) { + // '#if' here could be to guard 'case:' or statements in cases. + // If the next non-directive line starts with 'case' or 'default', it is + // for 'case's. + Parser::BacktrackingScope Backtrack(*this); + do { + consumeToken(); + while (!Tok.isAtStartOfLine() && Tok.isNot(tok::eof)) + skipSingle(); + } while (Tok.isAny(tok::pound_if, tok::pound_elseif, tok::pound_else)); + return Tok.isAny(tok::kw_case, tok::kw_default); + } + return Tok.isAny(tok::kw_case, tok::kw_default); case BraceItemListKind::TopLevelCode: // When parsing the top level executable code for a module, if we parsed // some executable code, then we're done. We want to process (name bind, @@ -247,7 +258,7 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl &Entries, Tok.isNot(tok::kw_sil_witness_table) && Tok.isNot(tok::kw_sil_default_witness_table) && (isConditionalBlock || - !isTerminatorForBraceItemListKind(Tok, Kind, Entries))) { + !isTerminatorForBraceItemListKind(Kind, Entries))) { if (Kind == BraceItemListKind::TopLevelLibrary && skipExtraTopLevelRBraces()) continue; @@ -2151,36 +2162,20 @@ ParserResult Parser::parseStmtSwitch(LabeledStmtInfo LabelInfo) { SourceLoc lBraceLoc = consumeToken(tok::l_brace); SourceLoc rBraceLoc; - // If there are non-case-label statements at the start of the switch body, - // raise an error and recover by discarding them. - bool DiagnosedNotCoveredStmt = false; - while (!Tok.is(tok::kw_case) && !Tok.is(tok::kw_default) - && !Tok.is(tok::r_brace) && !Tok.is(tok::eof)) { - if (!DiagnosedNotCoveredStmt) { - diagnose(Tok, diag::stmt_in_switch_not_covered_by_case); - DiagnosedNotCoveredStmt = true; - } - skipSingle(); - } - - SmallVector cases; - bool parsedDefault = false; - bool parsedBlockAfterDefault = false; - while (Tok.is(tok::kw_case) || Tok.is(tok::kw_default)) { - // We cannot have additional cases after a default clause. Complain on - // the first offender. - if (parsedDefault && !parsedBlockAfterDefault) { - parsedBlockAfterDefault = true; - diagnose(Tok, diag::case_after_default); - } - - ParserResult Case = parseStmtCase(); - Status |= Case; - if (Case.isNonNull()) { - cases.push_back(Case.get()); - if (Case.get()->isDefault()) - parsedDefault = true; + SmallVector cases; + Status |= parseStmtCases(cases, /*IsActive=*/true); + + // We cannot have additional cases after a default clause. Complain on + // the first offender. + bool hasDefault = false; + for (auto Element : cases) { + if (!Element.is()) continue; + auto *CS = cast(Element.get()); + if (hasDefault) { + diagnose(CS->getLoc(), diag::case_after_default); + break; } + hasDefault |= CS->isDefault(); } if (parseMatchingToken(tok::r_brace, rBraceLoc, @@ -2193,6 +2188,51 @@ ParserResult Parser::parseStmtSwitch(LabeledStmtInfo LabelInfo) { lBraceLoc, cases, rBraceLoc, Context)); } +ParserStatus +Parser::parseStmtCases(SmallVectorImpl &cases, bool IsActive) { + ParserStatus Status; + while (Tok.isNot(tok::r_brace, tok::eof, + tok::pound_endif, tok::pound_elseif, tok::pound_else)) { + if (Tok.isAny(tok::kw_case, tok::kw_default)) { + ParserResult Case = parseStmtCase(IsActive); + Status |= Case; + if (Case.isNonNull()) + cases.emplace_back(Case.get()); + } else if (Tok.is(tok::pound_if)) { + // '#if' in 'case' position can enclose one or more 'case' or 'default' + // clauses. + auto IfConfigResult = parseIfConfig( + [&](SmallVectorImpl &Elements, bool IsActive) { + parseStmtCases(Elements, IsActive); + }); + Status |= IfConfigResult; + if (auto ICD = IfConfigResult.getPtrOrNull()) { + cases.emplace_back(ICD); + + for (auto &Entry : ICD->getActiveClauseElements()) { + if (Entry.is() && isa(Entry.get())) + // Don't hoist nested '#if'. + continue; + + assert(Entry.is() && isa(Entry.get())); + cases.push_back(Entry); + } + } + } else { + // If there are non-case-label statements at the start of the switch body, + // raise an error and recover by discarding them. + diagnose(Tok, diag::stmt_in_switch_not_covered_by_case); + + while (Tok.isNot(tok::r_brace, tok::eof, tok::pound_elseif, + tok::pound_else, tok::pound_endif) && + !isTerminatorForBraceItemListKind(BraceItemListKind::Case, {})) { + skipSingle(); + } + } + } + return Status; +} + static ParserStatus parseStmtCase(Parser &P, SourceLoc &CaseLoc, SmallVectorImpl &LabelItems, SmallVectorImpl &BoundDecls, @@ -2257,9 +2297,9 @@ parseStmtCaseDefault(Parser &P, SourceLoc &CaseLoc, return Status; } -ParserResult Parser::parseStmtCase() { +ParserResult Parser::parseStmtCase(bool IsActive) { // A case block has its own scope for variables bound out of the pattern. - Scope S(this, ScopeKind::CaseVars); + Scope S(this, ScopeKind::CaseVars, !IsActive); ParserStatus Status; diff --git a/lib/SILGen/SILGenPattern.cpp b/lib/SILGen/SILGenPattern.cpp index 30f751af8aeb5..2958730e95165 100644 --- a/lib/SILGen/SILGenPattern.cpp +++ b/lib/SILGen/SILGenPattern.cpp @@ -2616,7 +2616,7 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) { // We use std::vector because it supports emplace_back; moving a ClauseRow is // expensive. std::vector clauseRows; - clauseRows.reserve(S->getCases().size()); + clauseRows.reserve(S->getRawCases().size()); bool hasFallthrough = false; for (auto caseBlock : S->getCases()) { for (auto &labelItem : caseBlock->getCaseLabelItems()) { diff --git a/lib/Sema/DerivedConformanceCodingKey.cpp b/lib/Sema/DerivedConformanceCodingKey.cpp index def94bdad7d8a..055b9731944b3 100644 --- a/lib/Sema/DerivedConformanceCodingKey.cpp +++ b/lib/Sema/DerivedConformanceCodingKey.cpp @@ -271,7 +271,7 @@ deriveBodyCodingKey_enum_stringValue(AbstractFunctionDecl *strValDecl) { body = BraceStmt::create(C, SourceLoc(), ASTNode(returnStmt), SourceLoc()); } else { - SmallVector cases; + SmallVector cases; for (auto *elt : elements) { auto *pat = new (C) EnumElementPattern(TypeLoc::withoutLoc(enumType), SourceLoc(), SourceLoc(), @@ -336,7 +336,7 @@ deriveBodyCodingKey_init_stringValue(AbstractFunctionDecl *initDecl) { } auto *selfRef = createSelfDeclRef(initDecl); - SmallVector cases; + SmallVector cases; for (auto *elt : elements) { auto *litExpr = new (C) StringLiteralExpr(elt->getNameStr(), SourceRange(), /*Implicit=*/true); diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index f0d5ed71f6977..6460bd3162fb8 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -88,7 +88,7 @@ static DeclRefExpr *convertEnumToIndex(SmallVectorImpl &stmts, indexPat, nullptr, funcDecl); unsigned index = 0; - SmallVector cases; + SmallVector cases; for (auto elt : enumDecl->getAllElements()) { // generate: case .: auto pat = new (C) EnumElementPattern(TypeLoc::withoutLoc(enumType), diff --git a/lib/Sema/DerivedConformanceRawRepresentable.cpp b/lib/Sema/DerivedConformanceRawRepresentable.cpp index 9feaad69d6943..c4a6a14aaec7f 100644 --- a/lib/Sema/DerivedConformanceRawRepresentable.cpp +++ b/lib/Sema/DerivedConformanceRawRepresentable.cpp @@ -93,7 +93,7 @@ static void deriveBodyRawRepresentable_raw(AbstractFunctionDecl *toRawDecl) { Type enumType = parentDC->getDeclaredTypeInContext(); - SmallVector cases; + SmallVector cases; for (auto elt : enumDecl->getAllElements()) { auto pat = new (C) EnumElementPattern(TypeLoc::withoutLoc(enumType), SourceLoc(), SourceLoc(), @@ -198,7 +198,7 @@ deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl) { auto selfDecl = cast(initDecl)->getImplicitSelfDecl(); - SmallVector cases; + SmallVector cases; for (auto elt : enumDecl->getAllElements()) { auto litExpr = cloneRawLiteralExpr(C, elt->getRawValueExpr()); auto litPat = new (C) ExprPattern(litExpr, /*isResolved*/ true, diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index 203540d1faf8c..60633a6a232f1 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -863,11 +863,12 @@ class StmtChecker : public StmtVisitor { AddSwitchNest switchNest(*this); AddLabeledStmt labelNest(*this, S); - for (unsigned i = 0, e = S->getCases().size(); i < e; ++i) { - auto *caseBlock = S->getCases()[i]; + auto cases = S->getCases(); + for (auto i = cases.begin(), e = cases.end(); i != e; ++i) { + auto *caseBlock = *i; // Fallthrough transfers control to the next case block. In the // final case block, it is invalid. - FallthroughDest = i+1 == e ? nullptr : S->getCases()[i+1]; + FallthroughDest = std::next(i) == e ? nullptr : *std::next(i); for (auto &labelItem : caseBlock->getMutableCaseLabelItems()) { // Resolve the pattern in the label. diff --git a/lib/Sema/TypeCheckSwitchStmt.cpp b/lib/Sema/TypeCheckSwitchStmt.cpp index d5594cad23a21..0ccf00cfbdd1f 100644 --- a/lib/Sema/TypeCheckSwitchStmt.cpp +++ b/lib/Sema/TypeCheckSwitchStmt.cpp @@ -917,8 +917,7 @@ namespace { bool sawDowngradablePattern = false; bool sawRedundantPattern = false; SmallVector spaces; - for (unsigned i = 0, e = Switch->getCases().size(); i < e; ++i) { - auto *caseBlock = Switch->getCases()[i]; + for (auto *caseBlock : Switch->getCases()) { for (auto &caseItem : caseBlock->getCaseLabelItems()) { // 'where'-clauses on cases mean the case does not contribute to // the exhaustiveness of the pattern. diff --git a/test/Parse/ConditionalCompilation/switch_case.swift b/test/Parse/ConditionalCompilation/switch_case.swift new file mode 100644 index 0000000000000..8fe0da7d65b03 --- /dev/null +++ b/test/Parse/ConditionalCompilation/switch_case.swift @@ -0,0 +1,221 @@ +// RUN: %target-typecheck-verify-swift -D ENABLE_C + +enum E { + case A,B +#if ENABLE_C + case C +#endif +#if ENABLE_D + case D +#endif +} + +func foo(x: E, intVal: Int) { + // Active guarded case first. + switch x { +#if ENABLE_C + case .C: + break +#endif + case .A: + break + case .B: + break + } + + // Active guarded case last. + switch x { + case .A: + break + case .B: + break +#if ENABLE_C + case .C: + break +#endif + } + + // Active guarded case middle. + switch x { + case .A: + break +#if ENABLE_C + case .C: + break +#endif + default: + break + } + + // Active guarded case after default. + switch x { + case .A: + break + default: + break +#if ENABLE_C + case .C: // expected-error {{additional 'case' blocks cannot appear after the 'default' block of a 'switch'}} + break +#endif + } + + // Inactive guarded case after default. + switch x { + case .A: + break + default: + break +#if ENABLE_D + case .D: + break +#endif + } + + // #elseif. + switch x { + case .A: + break + case .B: + break +#if NEVER +#elseif ENABLE_C + case .C: + break +#endif + } + + // #else. + switch x { + case .A: + break + case .B: + break +#if !ENABLE_C +#else + case .C: + break +#endif + } + + // Nested #if. + switch x { + case .A: + break + case .B: + break +#if ENABLE_C + #if NEVER + #else + case .C: + break + #endif +#endif + } + + // Exhaustive check. + switch x { // expected-error {{switch must be exhaustive}} expected-note {{add missing case: '.C'}} + case .A: + break + case .B: + break +#if NEVER + case .C: + break +#endif + } + + // Exhaustive check 2. + switch x { +#if ENABLE_C + case .A: + break + case .B: + break + case .C: + break +#endif + } + + // Empty check. + switch intVal { // expected-error {{'switch' statement body must have at least one 'case' or 'default' block; do you want to add a default case?}} +#if NEVER + case 1: + break + case 2: + break + case 3: + break +#endif + } + + // Non-'case' statement in '#else' block. + switch x { + case .A: + break + case .B: + break +#if ENABLE_C + case .C: + break +#else + break // expected-error {{all statements inside a switch must be covered by a 'case' or 'default'}} +#endif + } + + // Non-'case' statement in '#if' block. + switch x { // expected-error {{switch must be exhaustive}} expected-note {{add missing case: '.C'}} + case .A: + break + case .B: + break +#if !ENABLE_C + break +#else + case .C: // expected-error {{'case' label can only appear inside a 'switch' statement}} expected-error {{extraneous '.' in enum 'case' declaration}} + break +#endif + } + + // '#if ... case' after non-covered statements. + switch x { + print() // expected-error {{all statements inside a switch must be covered by a 'case' or 'default'}} +#if ENABLE_C + case .NOT_EXIST: // expected-error {{pattern cannot match values of type 'E'}} + break + case .C: + break +#endif + case .A, .B: + break + } + + // '#if ... stmt' after non-covered statements. + switch x { + print() // expected-error {{all statements inside a switch must be covered by a 'case' or 'default'}} +#if true + print() +#endif +#if ENABLE_C + case .C: + break +#endif + case .A, .B: + break + } + + // 'fallthrough' target. + switch intVal { + case 1: + fallthrough // expected-error {{'fallthrough' cannot transfer control to a case label that declares variables}} +#if ENABLE_C + case let val: + break +#endif + case 2: + fallthrough // expected-error {{'fallthrough' without a following 'case' or 'default' block}} +#if NEVER + case 3: + break +#endif + } +} diff --git a/test/Parse/ConditionalCompilation/switch_case_executable.swift b/test/Parse/ConditionalCompilation/switch_case_executable.swift new file mode 100644 index 0000000000000..231fc3fdc00ca --- /dev/null +++ b/test/Parse/ConditionalCompilation/switch_case_executable.swift @@ -0,0 +1,107 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: %target-build-swift %s -o %t/test1 -module-name main -D PATTERN_1 +// RUN: %target-build-swift %s -o %t/test2 -module-name main -D PATTERN_2 +// RUN: %target-run %t/test1 | %FileCheck -check-prefix=CHECK -check-prefix=CHECK1 %s +// RUN: %target-run %t/test2 | %FileCheck -check-prefix=CHECK -check-prefix=CHECK2 %s + +// REQUIRES: executable_test + +//------------------------------------------------------------------------------ +print("START: switch toplevel") +// CHECK-LABEL: START: switch toplevel + +let val = 1 +switch val { + case 100: + break +#if PATTERN_1 + case 1: + print("output1") +#elseif PATTERN_2 + case 1: + print("output2") +#endif + default: + print("never") +} + +// CHECK1-NEXT: output1 +// CHECK2-NEXT: output2 + +print("END: switch toplevel") +// CHECK-NEXT: END: switch toplevel + + +//------------------------------------------------------------------------------ +print("START: switch func") +// CHECK-LABEL: START: switch func + +enum MyEnum { + case A, B +#if PATTERN_1 + case str(String) +#elseif PATTERN_2 + case int(Int) +#endif +} + +func test1(_ val: MyEnum) { + switch val { + case .A, .B: + print("never") +#if PATTERN_1 + case let .str(v): + print("output3 - " + v) +#elseif PATTERN_2 + case let .int(v): + print("output4 - \(v + 12)") +#endif + } +} + +#if PATTERN_1 +test1(.str("foo bar")) +#elseif PATTERN_2 +test1(.int(42)) +#endif +// CHECK1-NEXT: output3 - foo bar +// CHECK2-NEXT: output4 - 54 + +print("END: switch func") +// CHECK-NEXT: END: switch func + +//------------------------------------------------------------------------------ +print("START: func local") +// CHECK-LABEL: func local + +func test2(_ val: Int) -> () -> Void { + let ret: () -> Void + switch val { +#if PATTERN_1 + case let v: + struct Foo : CustomStringConvertible { + let val: Int + var description: String { return "Foo(\(val))" } + } + func fn() { + print("output5 - \(Foo(val:v))") + } + ret = fn +#elseif PATTERN_2 + case let v: + struct Bar : CustomStringConvertible { + let val: Int + var description: String { return "Bar(\(val))" } + } + ret = { print("output6 - \(Bar(val: v))") } +#endif + } + return ret +} + +test2(42)() +// CHECK1-NEXT: output5 - Foo(42) +// CHECK2-NEXT: output6 - Bar(42) + +print("END: func local") +// CHECK-NEXT: END: func local diff --git a/test/Parse/invalid.swift b/test/Parse/invalid.swift index 281c5cb0c3a1c..bf21c14fe1cc6 100644 --- a/test/Parse/invalid.swift +++ b/test/Parse/invalid.swift @@ -43,17 +43,6 @@ func testNotCoveredCase(x: Int) { default: break } - - switch x { // expected-error{{'switch' statement body must have at least one 'case' or 'default' block; do you want to add a default case?}} -#if true // expected-error {{all statements inside a switch must be covered by a 'case' or 'default'}} - case 1: - break - case "foobar": // ignored - break - default: - break -#endif - } } // rdar://18926814 From f6310b4b9bd3d20e66485521adc911aae0ec5a30 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Thu, 22 Jun 2017 12:10:08 +0900 Subject: [PATCH 2/2] [test] Add test executable test case for nested #if in switch cases --- .../switch_case_executable.swift | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/Parse/ConditionalCompilation/switch_case_executable.swift b/test/Parse/ConditionalCompilation/switch_case_executable.swift index 231fc3fdc00ca..2500972fde45a 100644 --- a/test/Parse/ConditionalCompilation/switch_case_executable.swift +++ b/test/Parse/ConditionalCompilation/switch_case_executable.swift @@ -105,3 +105,46 @@ test2(42)() print("END: func local") // CHECK-NEXT: END: func local + +//------------------------------------------------------------------------------ +print("START: nested directives") +// CHECK-LABEL: START: nested directives + +#if PATTERN_1 || PATTERN_2 +func test3() { +#if PATTERN_1 || PATTERN_2 + class Nested { +#if PATTERN_1 || PATTERN_2 + func foo(_ x: Int) { + switch x { +#if true +#if PATTERN_1 + case 0..<42: + print("output7 - 0..<42 \(x)") +#elseif PATTERN_2 + case 0..<42: + print("output8 - 0..<42 \(x)") +#else + case 0..<42: + print("NEVER") +#endif + default: + print("output9 - default \(x)") +#endif + } + } +#endif + } + Nested().foo(12) +#endif + Nested().foo(53) +} +#endif +test3() +// CHECK1-NEXT: output7 - 0..<42 12 +// CHECK1-NEXT: output9 - default 53 +// CHECK2-NEXT: output8 - 0..<42 12 +// CHECK2-NEXT: output9 - default 53 + +print("END: nested directives") +// CHECK-NEXT: END: nested directives