diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 57c5b94fd8d38..7f7b0a1162524 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1408,6 +1408,12 @@ ERROR(unsupported_conditional_compilation_expression_type,none, WARNING(swift3_unsupported_conditional_compilation_expression_type,none, "ignoring invalid conditional compilation expression, " "which will be rejected in future version of Swift", ()) +WARNING(swift3_conditional_compilation_expression_compound,none, + "ignoring parentheses in compound name, " + "which will be rejected in future version of Swift", ()) +WARNING(swift3_conditional_compilation_expression_precedence,none, + "future version of Swift have different rule for evaluating condition; " + "add parentheses to make the condition compatible with Swift4", ()) ERROR(unsupported_conditional_compilation_integer,none, "'%0' is not a valid conditional compilation expression, use '%1'", (StringRef, StringRef)) diff --git a/include/swift/Basic/Version.h b/include/swift/Basic/Version.h index 9c369999adc28..3e7cb240fb6de 100644 --- a/include/swift/Basic/Version.h +++ b/include/swift/Basic/Version.h @@ -114,9 +114,9 @@ class Version { Version asMajorVersion() const; /// Parse a version in the form used by the _compiler_version \#if condition. - static Version parseCompilerVersionString(StringRef VersionString, - SourceLoc Loc, - DiagnosticEngine *Diags); + static Optional parseCompilerVersionString(StringRef VersionString, + SourceLoc Loc, + DiagnosticEngine *Diags); /// Parse a generic version string of the format [0-9]+(.[0-9]+)* /// diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index 06f99aac9d790..9309d184490ae 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -1271,15 +1271,6 @@ class Parser { ParserResult parseStmtSwitch(LabeledStmtInfo LabelInfo); ParserResult parseStmtCase(); - /// Classify the condition of an #if directive according to whether it can - /// be evaluated statically. The first member of the pair indicates whether - /// parsing of the condition body should occur, the second contains the result - /// of evaluating the conditional expression. - static ConditionalCompilationExprState - classifyConditionalCompilationExpr(Expr *condition, - ASTContext &context, - DiagnosticEngine &diags); - //===--------------------------------------------------------------------===// // Generics Parsing diff --git a/include/swift/Parse/ParserResult.h b/include/swift/Parse/ParserResult.h index e419ff39c3466..6b18d864b548a 100644 --- a/include/swift/Parse/ParserResult.h +++ b/include/swift/Parse/ParserResult.h @@ -218,75 +218,6 @@ template ParserResult::ParserResult(ParserStatus Status) { setHasCodeCompletion(); } -enum class ConditionalCompilationExprKind { - Unknown, - Error, - OS, - Arch, - LanguageVersion, - CompilerVersion, - Binary, - Paren, - DeclRef, - Boolean, - Integer, - Import, -}; - -class ConditionalCompilationExprState { - - uint8_t ConditionActive : 1; - uint8_t Kind : 7; -public: - ConditionalCompilationExprState() : ConditionActive(false) { - setKind(ConditionalCompilationExprKind::Unknown); - } - - ConditionalCompilationExprState(bool ConditionActive, - ConditionalCompilationExprKind Kind) - : ConditionActive(ConditionActive) { - setKind(Kind); - } - - bool isConditionActive() const { - return ConditionActive; - } - - void setConditionActive(bool A) { - ConditionActive = A; - } - - ConditionalCompilationExprKind getKind() const { - return static_cast(Kind); - } - - void setKind(ConditionalCompilationExprKind K) { - Kind = static_cast(K); - assert(getKind() == K); - } - - bool shouldParse() const { - if (getKind() == ConditionalCompilationExprKind::Error) - return true; - return ConditionActive || - (getKind() != ConditionalCompilationExprKind::CompilerVersion && - getKind() != ConditionalCompilationExprKind::LanguageVersion); - } - - static ConditionalCompilationExprState error() { - return {false, ConditionalCompilationExprKind::Error}; - } -}; - -ConditionalCompilationExprState -operator&&(const ConditionalCompilationExprState lhs, - const ConditionalCompilationExprState rhs); -ConditionalCompilationExprState -operator||(const ConditionalCompilationExprState lhs, - const ConditionalCompilationExprState rhs); -ConditionalCompilationExprState -operator!(const ConditionalCompilationExprState Result); - } // namespace swift #endif // LLVM_SWIFT_PARSER_PARSER_RESULT_H diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 9c056677ee042..52cdf45c47b27 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -1042,12 +1042,16 @@ namespace { for (auto &Clause : ICD->getClauses()) { if (Clause.Cond) { PrintWithColorRAII(OS, ParenthesisColor) << '('; - OS << "#if:\n"; + OS << "#if:"; + if (Clause.isActive) OS << " active"; + OS << "\n"; printRec(Clause.Cond); } else { OS << '\n'; PrintWithColorRAII(OS, ParenthesisColor) << '('; - OS << "#else:\n"; + OS << "#else:"; + if (Clause.isActive) OS << " active"; + OS << "\n"; } for (auto D : Clause.Elements) { @@ -1416,10 +1420,15 @@ class PrintStmt : public StmtVisitor { OS.indent(Indent); if (Clause.Cond) { PrintWithColorRAII(OS, ParenthesisColor) << '('; - PrintWithColorRAII(OS, StmtColor) << "#if:\n"; + PrintWithColorRAII(OS, StmtColor) << "#if:"; + if (Clause.isActive) + PrintWithColorRAII(OS, DeclModifierColor) << " active"; + OS << '\n'; printRec(Clause.Cond); } else { PrintWithColorRAII(OS, StmtColor) << "#else"; + if (Clause.isActive) + PrintWithColorRAII(OS, DeclModifierColor) << " active"; } OS << '\n'; diff --git a/lib/Basic/Version.cpp b/lib/Basic/Version.cpp index 220f1e26a7ac6..043b59ba7d631 100644 --- a/lib/Basic/Version.cpp +++ b/lib/Basic/Version.cpp @@ -75,10 +75,10 @@ static void printFullRevisionString(raw_ostream &out) { #endif } -void splitVersionComponents( +static void splitVersionComponents( SmallVectorImpl> &SplitComponents, StringRef &VersionString, SourceLoc Loc, - DiagnosticEngine *Diags, bool skipQuote = false) { + bool skipQuote = false) { SourceLoc Start = (Loc.isValid() && skipQuote) ? Loc.getAdvancedLoc(1) : Loc; SourceLoc End = Start; @@ -99,7 +99,7 @@ void splitVersionComponents( } } -Version Version::parseCompilerVersionString( +Optional Version::parseCompilerVersionString( StringRef VersionString, SourceLoc Loc, DiagnosticEngine *Diags) { Version CV; @@ -107,6 +107,12 @@ Version Version::parseCompilerVersionString( llvm::raw_svector_ostream OS(digits); SmallVector, 5> SplitComponents; + splitVersionComponents(SplitComponents, VersionString, Loc, + /*skipQuote=*/true); + + uint64_t ComponentNumber; + bool isValidVersion = true; + auto checkVersionComponent = [&](unsigned Component, SourceRange Range) { unsigned limit = CV.Components.size() == 0 ? 9223371 : 999; @@ -114,16 +120,10 @@ Version Version::parseCompilerVersionString( if (Diags) Diags->diagnose(Range.Start, diag::compiler_version_component_out_of_range, limit); - else - llvm_unreachable("Compiler version component out of range"); + isValidVersion = false; } }; - splitVersionComponents(SplitComponents, VersionString, Loc, Diags, - /*skipQuote=*/true); - - uint64_t ComponentNumber; - for (size_t i = 0; i < SplitComponents.size(); ++i) { StringRef SplitComponent; SourceRange Range; @@ -133,20 +133,16 @@ Version Version::parseCompilerVersionString( if (SplitComponent.empty()) { if (Diags) Diags->diagnose(Range.Start, diag::empty_version_component); - else - llvm_unreachable("Found empty compiler version component"); + isValidVersion = false; continue; } // The second version component isn't used for comparison. if (i == 1) { if (!SplitComponent.equals("*")) { - if (Diags) { + if (Diags) Diags->diagnose(Range.Start, diag::unused_compiler_version_component) .fixItReplaceChars(Range.Start, Range.End, "*"); - } else { - llvm_unreachable("Expected * for second compiler version component"); - } } CV.Components.push_back(0); @@ -158,23 +154,20 @@ Version Version::parseCompilerVersionString( checkVersionComponent(ComponentNumber, Range); CV.Components.push_back(ComponentNumber); continue; - } else if (Diags) { - Diags->diagnose(Range.Start, - diag::version_component_not_number); } else { - llvm_unreachable("Invalid character in _compiler_version condition"); + if (Diags) + Diags->diagnose(Range.Start, diag::version_component_not_number); + isValidVersion = false; } } if (CV.Components.size() > 5) { - if (Diags) { + if (Diags) Diags->diagnose(Loc, diag::compiler_version_too_many_components); - } else { - llvm_unreachable("Compiler version must not have more than 5 components"); - } + isValidVersion = false; } - return CV; + return isValidVersion ? Optional(CV) : None; } Optional Version::parseVersionString(StringRef VersionString, diff --git a/lib/Parse/ParseIfConfig.cpp b/lib/Parse/ParseIfConfig.cpp index 378b897357e0c..c0071c9cc134c 100644 --- a/lib/Parse/ParseIfConfig.cpp +++ b/lib/Parse/ParseIfConfig.cpp @@ -14,6 +14,7 @@ // //===----------------------------------------------------------------------===// +#include "swift/AST/ASTVisitor.h" #include "swift/Parse/Parser.h" #include "swift/Basic/Defer.h" #include "swift/Basic/LangOptions.h" @@ -25,6 +26,529 @@ using namespace swift; +namespace { + +/// Get PlatformConditionKind from platform condition name. +static +Optional getPlatformConditionKind(StringRef Name) { + return llvm::StringSwitch>(Name) + .Case("os", PlatformConditionKind::OS) + .Case("arch", PlatformConditionKind::Arch) + .Case("_endian", PlatformConditionKind::Endianness) + .Case("_runtime", PlatformConditionKind::Runtime) + .Default(None); +} + +/// Extract source text of the expression. +static StringRef extractExprSource(SourceManager &SM, Expr *E) { + CharSourceRange Range = + Lexer::getCharSourceRangeFromSourceRange(SM, E->getSourceRange()); + return SM.extractText(Range); +} + + +/// The condition validator. +class ValidateIfConfigCondition : + public ExprVisitor { + ASTContext &Ctx; + DiagnosticEngine &D; + + bool HasError; + + /// Get the identifier string of the UnresolvedDeclRefExpr. + llvm::Optional getDeclRefStr(Expr *E, DeclRefKind Kind) { + auto UDRE = dyn_cast(E); + if (!UDRE || + !UDRE->hasName() || + UDRE->getRefKind() != Kind) + return None; + if (UDRE->getName().isCompoundName()) { + if (!Ctx.isSwiftVersion3()) + return None; + // Swift3 used to accept compound names; warn and return the basename. + D.diagnose(UDRE->getNameLoc().getLParenLoc(), + diag::swift3_conditional_compilation_expression_compound) + .fixItRemove({ UDRE->getNameLoc().getLParenLoc(), + UDRE->getNameLoc().getRParenLoc() }); + } + + return UDRE->getName().getBaseName().str(); + } + + Expr *diagnoseUnsupportedExpr(Expr *E) { + D.diagnose(E->getLoc(), + diag::unsupported_conditional_compilation_expression_type); + return nullptr; + } + + // Support '||' and '&&' operator. The procedence of '&&' is higher than '||'. + // Invalid operator and the next operand are diagnosed and removed from AST. + Expr *foldSequence(Expr *LHS, ArrayRef &S, bool isRecurse = false) { + assert(!S.empty() && ((S.size() & 1) == 0)); + + auto getNextOperator = [&]() -> llvm::Optional { + assert((S.size() & 1) == 0); + while (!S.empty()) { + auto Name = getDeclRefStr(S[0], DeclRefKind::BinaryOperator); + if (Name.hasValue() && (*Name == "||" || *Name == "&&")) + return Name; + + auto DiagID = isa(S[0]) + ? diag::unsupported_conditional_compilation_binary_expression + : diag::unsupported_conditional_compilation_expression_type; + D.diagnose(S[0]->getLoc(), DiagID); + HasError |= true; + // Consume invalid operator and the immediate RHS. + S = S.slice(2); + } + return None; + }; + + // Extract out the first operator name. + auto OpName = getNextOperator(); + if (!OpName.hasValue()) + // If failed, it's not a sequence anymore. + return LHS; + Expr *Op = S[0]; + + // We will definitely be consuming at least one operator. + // Pull out the prospective RHS and slice off the first two elements. + Expr *RHS = validate(S[1]); + S = S.slice(2); + + while (true) { + // Pull out the next binary operator. + auto NextOpName = getNextOperator(); + bool IsEnd = !NextOpName.hasValue(); + if (!IsEnd && *OpName == "||" && *NextOpName == "&&") { + RHS = foldSequence(RHS, S, /*isRecurse*/true); + continue; + } + + // Apply the operator with left-associativity by folding the first two + // operands. + TupleExpr *Arg = TupleExpr::create(Ctx, SourceLoc(), { LHS, RHS }, + { }, { }, SourceLoc(), + /*HasTrailingClosure=*/false, + /*Implicit=*/true); + LHS = new (Ctx) BinaryExpr(Op, Arg, /*implicit*/false); + + // If we don't have the next operator, we're done. + if (IsEnd) + break; + if (isRecurse && *OpName == "&&" && *NextOpName == "||") + break; + + OpName = NextOpName; + Op = S[0]; + RHS = validate(S[1]); + S = S.slice(2); + } + + return LHS; + } + + // In Swift3 mode, leave sequence as a sequence because it has strange + // evaluation rule. See 'EvaluateIfConfigCondition::visitSequenceExpr'. + Expr *validateSequence(ArrayRef &S) { + assert(Ctx.isSwiftVersion3()); + + SmallVector Filtered; + SmallVector AndIdxs; + Filtered.push_back(validate(S[0])); + S = S.slice(1); + + while (!S.empty()) { + auto OpName = getDeclRefStr(S[0], DeclRefKind::BinaryOperator); + if (!OpName.hasValue() || (*OpName != "||" && *OpName != "&&")) { + // Warning and ignore in Swift3 mode. + D.diagnose( + S[0]->getLoc(), + diag::swift3_unsupported_conditional_compilation_expression_type) + .highlight({ S[0]->getLoc(), S[1]->getEndLoc() }); + } else { + // Remember the start and end of '&&' sequence. + bool InAnd = (AndIdxs.size() & 1) == 1; + if ((*OpName == "&&" && !InAnd) || (*OpName == "||" && InAnd)) + AndIdxs.push_back(Filtered.size() - 1); + + Filtered.push_back(S[0]); + Filtered.push_back(validate(S[1])); + } + S = S.slice(2); + } + assert((Filtered.size() & 1) == 1); + + // If the last OpName is '&&', close it with a parenthesis, except if the + // operators are '&&' only. + if ((1 == (AndIdxs.size() & 1)) && AndIdxs.back() > 0) + AndIdxs.push_back(Filtered.size() - 1); + // Emit fix-its to make this sequence compatilble with Swift >=4 even in + // Swift3 mode. + if (AndIdxs.size() >= 2) { + assert((AndIdxs.size() & 1) == 0); + auto diag = D.diagnose( + Filtered[AndIdxs[0]]->getStartLoc(), + diag::swift3_conditional_compilation_expression_precedence); + for (unsigned i = 0, e = AndIdxs.size(); i < e; i += 2) { + diag.fixItInsert(Filtered[AndIdxs[i]]->getStartLoc(), "("); + diag.fixItInsertAfter(Filtered[AndIdxs[i + 1]]->getEndLoc(), ")"); + } + } + + if (Filtered.size() == 1) + return Filtered[0]; + return SequenceExpr::create(Ctx, Filtered); + } + +public: + ValidateIfConfigCondition(ASTContext &Ctx, DiagnosticEngine &D) + : Ctx(Ctx), D(D), HasError(false) {} + + // Explicit configuration flag. + Expr *visitUnresolvedDeclRefExpr(UnresolvedDeclRefExpr *E) { + if (!getDeclRefStr(E, DeclRefKind::Ordinary).hasValue()) + return diagnoseUnsupportedExpr(E); + return E; + } + + // 'true' or 'false' constant. + Expr *visitBooleanLiteralExpr(BooleanLiteralExpr *E) { + return E; + } + + // '0' and '1' are warned, but we accept it. + Expr *visitIntegerLiteralExpr(IntegerLiteralExpr *E) { + if (E->isNegative() || + (E->getDigitsText() != "0" && E->getDigitsText() != "1")) { + return diagnoseUnsupportedExpr(E); + } + // "#if 0" isn't valid, but it is common, so recognize it and handle it + // with a fixit. + StringRef replacement = E->getDigitsText() == "0" ? "false" :"true"; + D.diagnose(E->getLoc(), diag::unsupported_conditional_compilation_integer, + E->getDigitsText(), replacement) + .fixItReplace(E->getLoc(), replacement); + return E; + } + + // Platform conditions. + Expr *visitCallExpr(CallExpr *E) { + auto KindName = getDeclRefStr(E->getFn(), DeclRefKind::Ordinary); + if (!KindName.hasValue()) { + D.diagnose(E->getLoc(), diag::unsupported_platform_condition_expression); + return nullptr; + } + + auto *ArgP = dyn_cast(E->getArg()); + if (!ArgP) { + D.diagnose(E->getLoc(), diag::platform_condition_expected_one_argument); + return nullptr; + } + Expr *Arg = ArgP->getSubExpr(); + + // '_compiler_version' '(' string-literal ')' + if (*KindName == "_compiler_version") { + auto SLE = dyn_cast(Arg); + if (!SLE) { + D.diagnose(Arg->getLoc(), + diag::unsupported_platform_condition_argument, + "string literal"); + return nullptr; + } + + auto ValStr = SLE->getValue(); + if (ValStr.empty()) { + D.diagnose(SLE->getLoc(), diag::empty_version_string); + return nullptr; + } + + auto Val = version::Version::parseCompilerVersionString( + SLE->getValue(), SLE->getLoc(), &D); + if(!Val.hasValue()) + return nullptr; + return E; + } + + // 'swift' '(' '>=' float-literal ( '.' integer-literal )* ')' + if (*KindName == "swift") { + auto PUE = dyn_cast(Arg); + llvm::Optional PrefixName = PUE ? + getDeclRefStr(PUE->getFn(), DeclRefKind::PrefixOperator) : None; + if (!PrefixName || *PrefixName != ">=") { + D.diagnose(Arg->getLoc(), + diag::unsupported_platform_condition_argument, + "a unary comparison, such as '>=2.2'"); + return nullptr; + } + auto versionString = extractExprSource(Ctx.SourceMgr, PUE->getArg()); + auto Val = version::Version::parseVersionString( + versionString, PUE->getArg()->getStartLoc(), &D); + if(!Val.hasValue()) + return nullptr; + return E; + } + + // ( 'os' | 'arch' | '_endian' | '_runtime' ) '(' identifier ')'' + auto Kind = getPlatformConditionKind(*KindName); + if (!Kind.hasValue()) { + D.diagnose(E->getLoc(), diag::unsupported_platform_condition_expression); + return nullptr; + } + + auto ArgStr = getDeclRefStr(Arg, DeclRefKind::Ordinary); + if (!ArgStr.hasValue()) { + D.diagnose(E->getLoc(), diag::unsupported_platform_condition_argument, + "identifier"); + return nullptr; + } + + // FIXME: Perform the replacement macOS -> OSX elsewhere. + if (Kind == PlatformConditionKind::OS && *ArgStr == "macOS") { + *ArgStr = "OSX"; + ArgP->setSubExpr( + new (Ctx) UnresolvedDeclRefExpr(Ctx.getIdentifier(*ArgStr), + DeclRefKind::Ordinary, + DeclNameLoc(Arg->getLoc()))); + } + + std::vector suggestions; + if (!LangOptions::checkPlatformConditionSupported(*Kind, *ArgStr, + suggestions)) { + if (Kind == PlatformConditionKind::Runtime) { + // Error for _runtime() + D.diagnose(Arg->getLoc(), + diag::unsupported_platform_runtime_condition_argument); + return nullptr; + } + StringRef DiagName; + switch (*Kind) { + case PlatformConditionKind::OS: + DiagName = "operating system"; break; + case PlatformConditionKind::Arch: + DiagName = "architecture"; break; + case PlatformConditionKind::Endianness: + DiagName = "endianness"; break; + case PlatformConditionKind::Runtime: + llvm_unreachable("handled above"); + } + auto Loc = Arg->getLoc(); + D.diagnose(Loc, diag::unknown_platform_condition_argument, + DiagName, *KindName); + for (auto suggestion : suggestions) + D.diagnose(Loc, diag::note_typo_candidate, suggestion) + .fixItReplace(Arg->getSourceRange(), suggestion); + return nullptr; + } + + return E; + } + + // Grouped condition. e.g. '(FLAG)' + Expr *visitParenExpr(ParenExpr *E) { + E->setSubExpr(validate(E->getSubExpr())); + return E; + } + + // Prefix '!'. Other prefix operators are rejected. + Expr *visitPrefixUnaryExpr(PrefixUnaryExpr *E) { + auto OpName = getDeclRefStr(E->getFn(), DeclRefKind::PrefixOperator); + if (!OpName.hasValue() || *OpName != "!") { + D.diagnose(E->getLoc(), + diag::unsupported_conditional_compilation_unary_expression); + return nullptr; + } + E->setArg(validate(E->getArg())); + return E; + } + + // Fold sequence expression for non-Swift3 mode. + Expr *visitSequenceExpr(SequenceExpr *E) { + ArrayRef Elts = E->getElements(); + Expr *foldedExpr; + if (Ctx.isSwiftVersion3()) { + foldedExpr = validateSequence(Elts); + } else { + auto LHS = validate(Elts[0]); + Elts = Elts.slice(1); + foldedExpr = foldSequence(LHS, Elts); + } + assert(Elts.empty()); + return foldedExpr; + } + + // Other expression types are unsupported. + Expr *visitExpr(Expr *E) { + return diagnoseUnsupportedExpr(E); + } + + Expr *validate(Expr *E) { + if (auto E2 = visit(E)) + return E2; + HasError |= true; + return E; + } + + bool hasError() const { + return HasError; + } +}; + +/// Validate and modify the condition expression. +/// Returns \c true if the condition contains any error. +static bool validateIfConfigCondition(Expr *&condition, + ASTContext &Context, + DiagnosticEngine &D) { + ValidateIfConfigCondition Validator(Context, D); + condition = Validator.validate(condition); + return Validator.hasError(); +} + +/// The condition evaluator. +/// The condition must be validated with validateIfConfigCondition(). +class EvaluateIfConfigCondition : + public ExprVisitor { + ASTContext &Ctx; + + /// Get the identifier string from an \c Expr assuming it's an + /// \c UnresolvedDeclRefExpr. + StringRef getDeclRefStr(Expr *E) { + return cast(E)->getName().getBaseName().str(); + } + +public: + EvaluateIfConfigCondition(ASTContext &Ctx) : Ctx(Ctx) {} + + bool visitBooleanLiteralExpr(BooleanLiteralExpr *E) { + return E->getValue(); + } + + bool visitIntegerLiteralExpr(IntegerLiteralExpr *E) { + return E->getDigitsText() != "0"; + } + + bool visitUnresolvedDeclRefExpr(UnresolvedDeclRefExpr *E) { + auto Name = getDeclRefStr(E); + return Ctx.LangOpts.isCustomConditionalCompilationFlagSet(Name); + } + + bool visitCallExpr(CallExpr *E) { + auto KindName = getDeclRefStr(E->getFn()); + auto *Arg = cast(E->getArg())->getSubExpr(); + + if (KindName == "_compiler_version") { + auto Str = cast(Arg)->getValue(); + auto Val = version::Version::parseCompilerVersionString( + Str, SourceLoc(), nullptr).getValue(); + auto thisVersion = version::Version::getCurrentCompilerVersion(); + return thisVersion >= Val; + } else if (KindName == "swift") { + auto PUE = cast(Arg); + auto Str = extractExprSource(Ctx.SourceMgr, PUE->getArg()); + auto Val = version::Version::parseVersionString( + Str, SourceLoc(), nullptr).getValue(); + auto thisVersion = Ctx.LangOpts.EffectiveLanguageVersion; + return thisVersion >= Val; + } + + auto Val = getDeclRefStr(Arg); + auto Kind = getPlatformConditionKind(KindName).getValue(); + auto Target = Ctx.LangOpts.getPlatformConditionValue(Kind); + return Target == Val; + } + + bool visitPrefixUnaryExpr(PrefixUnaryExpr *E) { + return !visit(E->getArg()); + } + + bool visitParenExpr(ParenExpr *E) { + return visit(E->getSubExpr()); + } + + bool visitBinaryExpr(BinaryExpr *E) { + assert(!Ctx.isSwiftVersion3() && "BinaryExpr in Swift3 mode"); + auto OpName = getDeclRefStr(E->getFn()); + auto Args = E->getArg()->getElements(); + if (OpName == "||") return visit(Args[0]) || visit(Args[1]); + if (OpName == "&&") return visit(Args[0]) && visit(Args[1]); + llvm_unreachable("unsupported binary operator"); + } + + bool visitSequenceExpr(SequenceExpr *E) { + assert(Ctx.isSwiftVersion3() && "SequenceExpr in non-Swift3 mode"); + ArrayRef Elems = E->getElements(); + auto Result = visit(Elems[0]); + Elems = Elems.slice(1); + while (!Elems.empty()) { + auto OpName = getDeclRefStr(Elems[0]); + + if (OpName == "||") { + Result = Result || visit(Elems[1]); + if (Result) + // Note that this is the Swift3 behavior. + // e.g. 'false || true && false' evaluates to 'true'. + return true; + } else if (OpName == "&&") { + Result = Result && visit(Elems[1]); + if (!Result) + // Ditto. + // e.g. 'false && true || true' evaluates to 'false'. + return false; + } else { + llvm_unreachable("must be removed in validation phase"); + } + Elems = Elems.slice(2); + } + return Result; + } + + bool visitExpr(Expr *E) { llvm_unreachable("Unvalidated condition?"); } +}; + +/// Evaluate the condition. +/// \c true if success, \c false if failed. +static bool evaluateIfConfigCondition(Expr *Condition, ASTContext &Context) { + return EvaluateIfConfigCondition(Context).visit(Condition); +} + +/// Version condition checker. +class IsVersionIfConfigCondition : + public ExprVisitor { + + /// Get the identifier string from an \c Expr assuming it's an + /// \c UnresolvedDeclRefExpr. + StringRef getDeclRefStr(Expr *E) { + return cast(E)->getName().getBaseName().str(); + } + +public: + IsVersionIfConfigCondition() {} + + bool visitBinaryExpr(BinaryExpr *E) { + auto OpName = getDeclRefStr(E->getFn()); + auto Args = E->getArg()->getElements(); + if (OpName == "||") return visit(Args[0]) && visit(Args[1]); + if (OpName == "&&") return visit(Args[0]) || visit(Args[1]); + llvm_unreachable("unsupported binary operator"); + } + + bool visitCallExpr(CallExpr *E) { + auto KindName = getDeclRefStr(E->getFn()); + return KindName == "_compiler_version" || KindName == "swift"; + } + + bool visitPrefixUnaryExpr(PrefixUnaryExpr *E) { return visit(E->getArg()); } + bool visitParenExpr(ParenExpr *E) { return visit(E->getSubExpr()); } + bool visitExpr(Expr *E) { return false; } +}; + +/// Returns \c true if the condition is a version check. +static bool isVersionIfConfigCondition(Expr *Condition) { + return IsVersionIfConfigCondition().visit(Condition); +} + +} // end anonymous namespace + /// Parse and populate a list of #if/#elseif/#else/#endif clauses. /// Delegate callback function to parse elements in the blocks. template @@ -36,15 +560,16 @@ static ParserStatus parseIfConfig( P, P.Tok.getLoc(), Parser::StructureMarkerKind::IfConfig); bool foundActive = false; - ConditionalCompilationExprState ConfigState; + bool isVersionCondition = false; while (1) { bool isElse = P.Tok.is(tok::pound_else); SourceLoc ClauseLoc = P.consumeToken(); Expr *Condition = nullptr; + bool isActive = false; // Parse and evaluate the directive. if (isElse) { - ConfigState.setConditionActive(!foundActive); + isActive = !foundActive; } else { llvm::SaveAndRestore S(P.InPoundIfEnvironment, true); ParserResult Result = P.parseExprSequence(diag::expected_expr, @@ -52,17 +577,19 @@ static ParserStatus parseIfConfig( /*isForDirective*/true); if (Result.isNull()) return makeParserError(); - Condition = Result.get(); - - // Evaluate the condition, to validate it. - ConfigState = P.classifyConditionalCompilationExpr(Condition, P.Context, - P.Diags); - if (foundActive) - ConfigState.setConditionActive(false); + if (validateIfConfigCondition(Condition, P.Context, P.Diags)) { + // Error in the condition; + isActive = false; + isVersionCondition = false; + } else if (!foundActive) { + // Evaludate the condition only if we haven't found any active one. + isActive = evaluateIfConfigCondition(Condition, P.Context); + isVersionCondition = isVersionIfConfigCondition(Condition); + } } - foundActive |= ConfigState.isConditionActive(); + foundActive |= isActive; if (!P.Tok.isAtStartOfLine() && P.Tok.isNot(tok::eof)) { P.diagnose(P.Tok.getLoc(), @@ -71,8 +598,8 @@ static ParserStatus parseIfConfig( // Parse elements SmallVector Elements; - if (ConfigState.shouldParse()) { - parseElements(Elements, ConfigState.isConditionActive()); + if (isActive || !isVersionCondition) { + parseElements(Elements, isActive); } else { DiagnosticTransaction DT(P.Diags); P.skipUntilConditionalBlockClose(); @@ -81,7 +608,7 @@ static ParserStatus parseIfConfig( Clauses.push_back(IfConfigClause(ClauseLoc, Condition, P.Context.AllocateCopy(Elements), - ConfigState.isConditionActive())); + isActive)); if (P.Tok.isNot(tok::pound_elseif, tok::pound_else)) break; @@ -94,6 +621,7 @@ static ParserStatus parseIfConfig( return makeParserSuccess(); } +/// Parse #if ... #endif in declarations position. ParserResult Parser::parseDeclIfConfig(ParseDeclOptions Flags) { SmallVector, 4> Clauses; SourceLoc EndLoc; @@ -131,6 +659,7 @@ ParserResult Parser::parseDeclIfConfig(ParseDeclOptions Flags) { return makeParserResult(ICD); } +/// Parse #if ... #endif in statements position. ParserResult Parser::parseStmtIfConfig(BraceItemListKind Kind) { SmallVector, 4> Clauses; SourceLoc EndLoc; @@ -149,279 +678,3 @@ ParserResult Parser::parseStmtIfConfig(BraceItemListKind Kind) { EndLoc, HadMissingEnd); return makeParserResult(ICS); } - -// Evaluate a subset of expression types suitable for build configuration -// conditional expressions. The accepted expression types are: -// - The magic constants "true" and "false". -// - Named decl ref expressions ("FOO") -// - Parenthesized expressions ("(FOO)") -// - Binary "&&" or "||" operations applied to other build configuration -// conditional expressions -// - Unary "!" expressions applied to other build configuration conditional -// expressions -// - Single-argument call expressions, where the function being invoked is a -// supported target configuration (currently "os", "arch", and -// "_compiler_version"), and whose argument is a named decl ref expression -ConditionalCompilationExprState -Parser::classifyConditionalCompilationExpr(Expr *condition, - ASTContext &Context, - DiagnosticEngine &D) { - assert(condition && "Cannot classify a NULL condition expression!"); - - // Evaluate a ParenExpr. - if (auto *PE = dyn_cast(condition)) - return classifyConditionalCompilationExpr(PE->getSubExpr(), Context, D); - - // Evaluate a "&&" or "||" expression. - if (auto *SE = dyn_cast(condition)) { - // Check for '&&' or '||' as the expression type. - if (SE->getNumElements() < 3) { - D.diagnose(SE->getLoc(), - diag::unsupported_conditional_compilation_binary_expression); - return ConditionalCompilationExprState::error(); - } - // Before type checking, chains of binary expressions will not be fully - // parsed, so associativity has not yet been encoded in the subtree. - auto elements = SE->getElements(); - auto numElements = SE->getNumElements(); - size_t iOperator = 1; - size_t iOperand = 2; - - auto result = classifyConditionalCompilationExpr(elements[0], Context, D); - - while (iOperand < numElements) { - - if (auto *UDREOp = dyn_cast(elements[iOperator])) { - auto name = UDREOp->getName().getBaseName().str(); - - if (name.equals("||") || name.equals("&&")) { - auto rhs = classifyConditionalCompilationExpr(elements[iOperand], - Context, D); - - if (name.equals("||")) { - result = result || rhs; - if (result.isConditionActive()) - break; - } - - if (name.equals("&&")) { - result = result && rhs; - if (!result.isConditionActive()) - break; - } - } else { - D.diagnose( - SE->getLoc(), - diag::unsupported_conditional_compilation_binary_expression); - return ConditionalCompilationExprState::error(); - } - } else { - // Swift3 didn't have this branch. the operator and the RHS are - // silently ignored. - if (!Context.isSwiftVersion3()) { - D.diagnose( - elements[iOperator]->getLoc(), - diag::unsupported_conditional_compilation_expression_type); - return ConditionalCompilationExprState::error(); - } else { - SourceRange ignoredRange(elements[iOperator]->getLoc(), - elements[iOperand]->getEndLoc()); - D.diagnose( - elements[iOperator]->getLoc(), - diag::swift3_unsupported_conditional_compilation_expression_type) - .highlight(ignoredRange); - } - } - - iOperator += 2; - iOperand += 2; - } - - return result; - } - - // Evaluate a named reference expression. - if (auto *UDRE = dyn_cast(condition)) { - auto name = UDRE->getName().getBaseName().str(); - return {Context.LangOpts.isCustomConditionalCompilationFlagSet(name), - ConditionalCompilationExprKind::DeclRef}; - } - - // Evaluate a Boolean literal. - if (auto *boolLit = dyn_cast(condition)) { - return {boolLit->getValue(), ConditionalCompilationExprKind::Boolean}; - } - - // Evaluate a negation (unary "!") expression. - if (auto *PUE = dyn_cast(condition)) { - // If the PUE is not a negation expression, return false - auto name = - cast(PUE->getFn())->getName().getBaseName().str(); - if (name != "!") { - D.diagnose(PUE->getLoc(), - diag::unsupported_conditional_compilation_unary_expression); - return ConditionalCompilationExprState::error(); - } - - return !classifyConditionalCompilationExpr(PUE->getArg(), Context, D); - } - - // Evaluate a target config call expression. - if (auto *CE = dyn_cast(condition)) { - // look up target config, and compare value - auto fnNameExpr = dyn_cast(CE->getFn()); - - // Get the arg, which should be in a paren expression. - if (!fnNameExpr) { - D.diagnose(CE->getLoc(), diag::unsupported_platform_condition_expression); - return ConditionalCompilationExprState::error(); - } - - auto fnName = fnNameExpr->getName().getBaseName().str(); - - auto *PE = dyn_cast(CE->getArg()); - if (!PE) { - auto diag = D.diagnose(CE->getLoc(), - diag::platform_condition_expected_one_argument); - return ConditionalCompilationExprState::error(); - } - - if (!fnName.equals("arch") && !fnName.equals("os") && - !fnName.equals("_endian") && - !fnName.equals("_runtime") && - !fnName.equals("swift") && - !fnName.equals("_compiler_version")) { - D.diagnose(CE->getLoc(), diag::unsupported_platform_condition_expression); - return ConditionalCompilationExprState::error(); - } - - if (fnName.equals("_compiler_version")) { - if (auto SLE = dyn_cast(PE->getSubExpr())) { - if (SLE->getValue().empty()) { - D.diagnose(CE->getLoc(), diag::empty_version_string); - return ConditionalCompilationExprState::error(); - } - auto versionRequirement = - version::Version::parseCompilerVersionString(SLE->getValue(), - SLE->getLoc(), - &D); - auto thisVersion = version::Version::getCurrentCompilerVersion(); - auto VersionNewEnough = thisVersion >= versionRequirement; - return {VersionNewEnough, - ConditionalCompilationExprKind::CompilerVersion}; - } else { - D.diagnose(CE->getLoc(), diag::unsupported_platform_condition_argument, - "string literal"); - return ConditionalCompilationExprState::error(); - } - } else if (fnName.equals("swift")) { - auto PUE = dyn_cast(PE->getSubExpr()); - if (!PUE) { - D.diagnose(PE->getSubExpr()->getLoc(), - diag::unsupported_platform_condition_argument, - "a unary comparison, such as '>=2.2'"); - return ConditionalCompilationExprState::error(); - } - - auto prefix = dyn_cast(PUE->getFn()); - auto versionArg = PUE->getArg(); - auto versionStartLoc = versionArg->getStartLoc(); - auto endLoc = Lexer::getLocForEndOfToken(Context.SourceMgr, - versionArg->getSourceRange().End); - CharSourceRange versionCharRange(Context.SourceMgr, versionStartLoc, - endLoc); - auto versionString = Context.SourceMgr.extractText(versionCharRange); - - auto versionRequirement = - version::Version::parseVersionString(versionString, - versionStartLoc, - &D); - - if (!versionRequirement.hasValue()) - return ConditionalCompilationExprState::error(); - - auto thisVersion = Context.LangOpts.EffectiveLanguageVersion; - - if (!prefix->getName().getBaseName().str().equals(">=")) { - D.diagnose(PUE->getFn()->getLoc(), - diag::unexpected_version_comparison_operator) - .fixItReplace(PUE->getFn()->getLoc(), ">="); - return ConditionalCompilationExprState::error(); - } - - auto VersionNewEnough = thisVersion >= versionRequirement.getValue(); - return {VersionNewEnough, - ConditionalCompilationExprKind::LanguageVersion}; - } else { - if (auto UDRE = dyn_cast(PE->getSubExpr())) { - // The sub expression should be an UnresolvedDeclRefExpr (we won't - // tolerate extra parens). - auto argumentIdent = UDRE->getName().getBaseName(); - auto argument = argumentIdent.str(); - PlatformConditionKind Kind = - llvm::StringSwitch(fnName) - .Case("os", PlatformConditionKind::OS) - .Case("arch", PlatformConditionKind::Arch) - .Case("_endian", PlatformConditionKind::Endianness) - .Case("_runtime", PlatformConditionKind::Runtime); - - // FIXME: Perform the replacement macOS -> OSX elsewhere. - if (Kind == PlatformConditionKind::OS && argument == "macOS") - argument = "OSX"; - - std::vector suggestions; - if (!LangOptions::checkPlatformConditionSupported(Kind, argument, - suggestions)) { - if (Kind == PlatformConditionKind::Runtime) { - // Error for _runtime() - D.diagnose(UDRE->getLoc(), - diag::unsupported_platform_runtime_condition_argument); - return ConditionalCompilationExprState::error(); - } - StringRef DiagName; - switch (Kind) { - case PlatformConditionKind::OS: - DiagName = "operating system"; break; - case PlatformConditionKind::Arch: - DiagName = "architecture"; break; - case PlatformConditionKind::Endianness: - DiagName = "endianness"; break; - case PlatformConditionKind::Runtime: - llvm_unreachable("handled above"); - } - D.diagnose(UDRE->getLoc(), diag::unknown_platform_condition_argument, - DiagName, fnName); - for (const StringRef &suggestion : suggestions) { - D.diagnose(UDRE->getLoc(), diag::note_typo_candidate, suggestion) - .fixItReplace(UDRE->getSourceRange(), suggestion); - } - } - - auto target = Context.LangOpts.getPlatformConditionValue(Kind); - return {target == argument, ConditionalCompilationExprKind::DeclRef}; - } else { - D.diagnose(CE->getLoc(), diag::unsupported_platform_condition_argument, - "identifier"); - return ConditionalCompilationExprState::error(); - } - } - } - - // "#if 0" isn't valid, but it is common, so recognize it and handle it - // with a fixit elegantly. - if (auto *IL = dyn_cast(condition)) - if (IL->getDigitsText() == "0" || IL->getDigitsText() == "1") { - StringRef replacement = IL->getDigitsText() == "0" ? "false" :"true"; - D.diagnose(IL->getLoc(), diag::unsupported_conditional_compilation_integer, - IL->getDigitsText(), replacement) - .fixItReplace(IL->getLoc(), replacement); - return {IL->getDigitsText() == "1", - ConditionalCompilationExprKind::Integer}; - } - - - // If we've gotten here, it's an unsupported expression type. - D.diagnose(condition->getLoc(), - diag::unsupported_conditional_compilation_expression_type); - return ConditionalCompilationExprState::error(); -} diff --git a/lib/Parse/Parser.cpp b/lib/Parse/Parser.cpp index cbdd9b7f8e169..4853cffb90cf1 100644 --- a/lib/Parse/Parser.cpp +++ b/lib/Parse/Parser.cpp @@ -836,25 +836,6 @@ SourceFile &ParserUnit::getSourceFile() { return *Impl.SF; } -ConditionalCompilationExprState -swift::operator&&(ConditionalCompilationExprState lhs, - ConditionalCompilationExprState rhs) { - return {lhs.isConditionActive() && rhs.isConditionActive(), - ConditionalCompilationExprKind::Binary}; -} - -ConditionalCompilationExprState -swift::operator||(ConditionalCompilationExprState lhs, - ConditionalCompilationExprState rhs) { - return {lhs.isConditionActive() || rhs.isConditionActive(), - ConditionalCompilationExprKind::Binary}; -} - -ConditionalCompilationExprState -swift::operator!(ConditionalCompilationExprState state) { - return {!state.isConditionActive(), state.getKind()}; -} - ParsedDeclName swift::parseDeclName(StringRef name) { if (name.empty()) return ParsedDeclName(); diff --git a/test/Compatibility/conditional_compiliation_expr.swift b/test/Compatibility/conditional_compiliation_expr.swift index b26cd51ae8130..1c6e987b5fe59 100644 --- a/test/Compatibility/conditional_compiliation_expr.swift +++ b/test/Compatibility/conditional_compiliation_expr.swift @@ -1,5 +1,7 @@ // RUN: %target-typecheck-verify-swift -D FOO -swift-version 3 +// --------------------------------------------------------------------------- +// Invalid binary operation #if FOO = false // expected-warning @-1 {{ignoring invalid conditional compilation expression, which will be rejected in future version of Swift}} @@ -16,3 +18,103 @@ undefinedFunc() // ignored. #else undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}} #endif + +// --------------------------------------------------------------------------- +// SR-3663: The precedence and associativity of '||' and '&&'. +// See test/Parse/ConditionalCompilation/sequence.swift for Swift 4 behavior. + +// expected-warning @+1 {{future version of Swift have different rule for evaluating condition; add parentheses to make the condition compatible with Swift4}} {{14-14=(}} {{27-27=)}} +#if false || true && false +undefinedIf() // expected-error {{use of unresolved identifier 'undefinedIf'}} +#else +undefinedElse() +#endif + +// expected-warning @+1 {{future version of Swift have different rule for evaluating condition; add parentheses to make the condition compatible with Swift4}} {{5-5=(}} {{18-18=)}} +#if false && true || true +undefinedIf() +#else +undefinedElse() // expected-error {{use of unresolved identifier 'undefinedElse'}} +#endif + +// expected-warning @+1 {{future version of Swift have different rule for evaluating condition; add parentheses to make the condition compatible with Swift4}} {{14-14=(}} {{27-27=)}} +#if false || true && false || false +undefinedIf() // expected-error {{use of unresolved identifier 'undefinedIf'}} +#else +undefinedElse() +#endif + +// expected-warning @+1 {{future version of Swift have different rule for evaluating condition; add parentheses to make the condition compatible with Swift4}} {{5-5=(}} {{18-18=)}} {{22-22=(}} {{42-42=)}} +#if true && false || true && true && true +undefinedIf() +#else +undefinedElse() // expected-error {{use of unresolved identifier 'undefinedElse'}} +#endif + +// Accepted in Swift3. *source breaking* +#if false || true && try! Swift +// expected-error @-1 {{invalid conditional compilation expression}} +// expected-warning @-2 {{future version of Swift have different rule for evaluating condition; add parentheses to make the condition compatible with Swift4}} {{14-14=(}} {{32-32=)}} +undefinedIf() +#endif + +// --------------------------------------------------------------------------- +// SR-4032: "skip parsing" in non-active branch for version checks. +// See test/Parse/ConditionalCompilation/sequence_version.swift for Swift 4 behavior. + +#if !swift(>=2.2) +// There should be no error here. +foo bar +#else +let _: Int = 1 +#endif + +#if (swift(>=2.2)) +let _: Int = 1 +#else +// There should be no error here. +foo bar +#endif + +#if swift(>=99.0) || swift(>=88.1.1) +// There should be no error here. +foo bar baz // expected-error 2 {{consecutive statements}} +#else +undefinedElse() // expected-error {{use of unresolved identifier 'undefinedElse'}} +#endif + +#if swift(>=99.0) || FOO +undefinedIf() // expected-error {{use of unresolved identifier 'undefinedIf'}} +#else +undefinedElse() +#endif + +#if swift(>=99.0) && FOO +// There should be no error here. +foo bar baz // expected-error 2 {{consecutive statements}} +#else +undefinedElse() // expected-error {{use of unresolved identifier 'undefinedElse'}} +#endif + +#if FOO && swift(>=2.2) +undefinedIf() // expected-error {{use of unresolved identifier 'undefinedIf'}} +#else +// There should be no error here. +foo bar baz // expected-error 2 {{consecutive statements}} +#endif + +#if swift(>=2.2) && swift(>=1) +undefinedIf() // expected-error {{use of unresolved identifier 'undefinedIf'}} +#else +// There should be no error here. +foo bar baz // expected-error 2 {{consecutive statements}} +#endif + +// --------------------------------------------------------------------------- +// SR-4031: Compound name in compilation condition +// See test/Parse/ConditionalCompilation/compoundName_swift4.swift for Swfit 4 behavior + +#if BAR(_:) // expected-warning {{ignoring parentheses in compound name, which will be rejected in future version of Swift}} {{8-12=}} +#elseif os(x:)(macOS) // expected-warning {{ignoring parentheses in compound name, which will be rejected in future version of Swift}} {{11-15=}} +#elseif os(Linux(foo:bar:)) // expected-warning {{ignoring parentheses in compound name, which will be rejected in future version of Swift}} {{17-27=}} +#endif diff --git a/test/Parse/ConditionalCompilation/compoundName_swift4.swift b/test/Parse/ConditionalCompilation/compoundName_swift4.swift new file mode 100644 index 0000000000000..da8f1b3da7aa2 --- /dev/null +++ b/test/Parse/ConditionalCompilation/compoundName_swift4.swift @@ -0,0 +1,7 @@ +// RUN: %target-typecheck-verify-swift -swift-version 4 + +/// Reject compound names. +#if BAR(_:) // expected-error {{invalid conditional compilation expression}} +#elseif os(x:)(macOS) // expected-error {{unexpected platform condition (expected 'os', 'arch', or 'swift')}} +#elseif os(Linux(foo:bar:)) // expected-error {{unexpected platform condition argument: expected identifier}} +#endif diff --git a/test/Parse/ConditionalCompilation/identifierName.swift b/test/Parse/ConditionalCompilation/identifierName.swift index edd2efca6f796..f5889449acaaa 100644 --- a/test/Parse/ConditionalCompilation/identifierName.swift +++ b/test/Parse/ConditionalCompilation/identifierName.swift @@ -74,3 +74,4 @@ let BAR = { () -> Void in #if BAR #endif } + diff --git a/test/Parse/ConditionalCompilation/sequence.swift b/test/Parse/ConditionalCompilation/sequence.swift new file mode 100644 index 0000000000000..9768ef9f87228 --- /dev/null +++ b/test/Parse/ConditionalCompilation/sequence.swift @@ -0,0 +1,23 @@ +// RUN: %target-typecheck-verify-swift -swift-version 4 + +#if false || true && false +undefinedIf() +#else +undefinedElse() // expected-error {{use of unresolved identifier 'undefinedElse'}} +#endif + +#if false && true || true +undefinedIf() // expected-error {{use of unresolved identifier 'undefinedIf'}} +#else +undefinedElse() +#endif + +#if false || true && false || false +undefinedIf() +#else +undefinedElse() // expected-error {{use of unresolved identifier 'undefinedElse'}} +#endif + +// expected-error @+1 {{invalid conditional compilation expression}} +#if false || true && try! Swift +#endif diff --git a/test/Parse/ConditionalCompilation/sequence_version.swift b/test/Parse/ConditionalCompilation/sequence_version.swift new file mode 100644 index 0000000000000..aac770807c912 --- /dev/null +++ b/test/Parse/ConditionalCompilation/sequence_version.swift @@ -0,0 +1,49 @@ +// RUN: %target-typecheck-verify-swift -swift-version 4 -D FOO + +#if !swift(>=2.2) +// There should be no error here. +foo bar +#else +let _: Int = 1 +#endif + +#if (swift(>=2.2)) +let _: Int = 1 +#else +// There should be no error here. +foo bar +#endif + +#if swift(>=99.0) || swift(>=88.1.1) +// There should be no error here. +foo bar baz +#else +undefinedElse() // expected-error {{use of unresolved identifier 'undefinedElse'}} +#endif + +#if swift(>=99.0) || FOO +undefinedIf() // expected-error {{use of unresolved identifier 'undefinedIf'}} +#else +undefinedElse() +#endif + +#if swift(>=99.0) && FOO +// There should be no error here. +foo bar baz +#else +undefinedElse() // expected-error {{use of unresolved identifier 'undefinedElse'}} +#endif + +#if FOO && swift(>=2.2) +undefinedIf() // expected-error {{use of unresolved identifier 'undefinedIf'}} +#else +// There should be no error here. +foo bar baz +#endif + +#if swift(>=2.2) && swift(>=1) +undefinedIf() // expected-error {{use of unresolved identifier 'undefinedIf'}} +#else +// There should be no error here. +foo bar baz +#endif diff --git a/unittests/Parse/BuildConfigTests.cpp b/unittests/Parse/BuildConfigTests.cpp index cf7c2df1d3840..639e6527e9899 100644 --- a/unittests/Parse/BuildConfigTests.cpp +++ b/unittests/Parse/BuildConfigTests.cpp @@ -9,7 +9,7 @@ using namespace llvm; class CompilerVersionTest : public ::testing::Test {}; class VersionTest : public ::testing::Test{}; -version::Version CV(const char *VersionString) { +Optional CV(const char *VersionString) { return version::Version::parseCompilerVersionString(VersionString, SourceLoc(), nullptr); @@ -23,13 +23,13 @@ Optional V(const char *VersionString) { TEST_F(CompilerVersionTest, VersionComparison) { auto currentVersion = version::Version::getCurrentCompilerVersion(); - EXPECT_GE(CV("700"), CV("602")); - EXPECT_GE(CV("700.*"), CV("700.*")); - EXPECT_GE(CV("700.*.1"), CV("700.*.0")); - EXPECT_GE(CV("700.*.23"), CV("700.*.21")); - EXPECT_GE(CV("700.*.1.1.0"), CV("700.*.1.1")); + EXPECT_GE(CV("700").getValue(), CV("602").getValue()); + EXPECT_GE(CV("700.*").getValue(), CV("700.*").getValue()); + EXPECT_GE(CV("700.*.1").getValue(), CV("700.*.0").getValue()); + EXPECT_GE(CV("700.*.23").getValue(), CV("700.*.21").getValue()); + EXPECT_GE(CV("700.*.1.1.0").getValue(), CV("700.*.1.1").getValue()); EXPECT_GE(currentVersion, currentVersion); - EXPECT_GE(currentVersion, CV("9223371.*.999.999.999")); + EXPECT_GE(currentVersion, CV("9223371.*.999.999.999").getValue()); } TEST_F(VersionTest, VersionComparison) {