| //===--- ParseIfConfig.cpp - Swift Language Parser for #if directives -----===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Conditional Compilation Block Parsing and AST Building |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "swift/Parse/Parser.h" |
| |
| #include "swift/AST/ASTVisitor.h" |
| #include "swift/Basic/Defer.h" |
| #include "swift/Basic/LangOptions.h" |
| #include "swift/Basic/Version.h" |
| #include "swift/Parse/Lexer.h" |
| #include "swift/Parse/SyntaxParsingContext.h" |
| #include "swift/Syntax/SyntaxFactory.h" |
| #include "swift/Syntax/TokenSyntax.h" |
| #include "llvm/ADT/StringSwitch.h" |
| #include "llvm/Support/Compiler.h" |
| #include "llvm/Support/SaveAndRestore.h" |
| |
| using namespace swift; |
| using namespace swift::syntax; |
| |
| namespace { |
| |
| /// Get PlatformConditionKind from platform condition name. |
| static |
| Optional<PlatformConditionKind> getPlatformConditionKind(StringRef Name) { |
| return llvm::StringSwitch<llvm::Optional<PlatformConditionKind>>(Name) |
| .Case("os", PlatformConditionKind::OS) |
| .Case("arch", PlatformConditionKind::Arch) |
| .Case("_endian", PlatformConditionKind::Endianness) |
| .Case("_runtime", PlatformConditionKind::Runtime) |
| .Case("canImport", PlatformConditionKind::CanImport) |
| .Case("targetEnvironment", PlatformConditionKind::TargetEnvironment) |
| .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<ValidateIfConfigCondition, Expr*> { |
| ASTContext &Ctx; |
| DiagnosticEngine &D; |
| |
| bool HasError; |
| |
| /// Get the identifier string of the UnresolvedDeclRefExpr. |
| llvm::Optional<StringRef> getDeclRefStr(Expr *E, DeclRefKind Kind) { |
| auto UDRE = dyn_cast<UnresolvedDeclRefExpr>(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().getBaseIdentifier().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<Expr*> &S, bool isRecurse = false) { |
| assert(!S.empty() && ((S.size() & 1) == 0)); |
| |
| auto getNextOperator = [&]() -> llvm::Optional<StringRef> { |
| 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<UnresolvedDeclRefExpr>(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<Expr *> &S) { |
| assert(Ctx.isSwiftVersion3()); |
| |
| SmallVector<Expr *, 3> Filtered; |
| SmallVector<unsigned, 2> 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<ParenExpr>(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<StringLiteralExpr>(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 )* ')' |
| // 'compiler' '(' '>=' float-literal ( '.' integer-literal )* ')' |
| if (*KindName == "swift" || *KindName == "compiler") { |
| auto PUE = dyn_cast<PrefixUnaryExpr>(Arg); |
| llvm::Optional<StringRef> 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' | 'canImport') '(' 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; |
| } |
| |
| std::vector<StringRef> 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; |
| } |
| |
| // Just a warning for other unsupported arguments. |
| 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::CanImport: |
| DiagName = "import conditional"; break; |
| case PlatformConditionKind::TargetEnvironment: |
| DiagName = "target environment"; 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 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<Expr*> 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<EvaluateIfConfigCondition, bool> { |
| ASTContext &Ctx; |
| |
| /// Get the identifier string from an \c Expr assuming it's an |
| /// \c UnresolvedDeclRefExpr. |
| StringRef getDeclRefStr(Expr *E) { |
| return cast<UnresolvedDeclRefExpr>(E)->getName().getBaseIdentifier().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<ParenExpr>(E->getArg())->getSubExpr(); |
| |
| if (KindName == "_compiler_version") { |
| auto Str = cast<StringLiteralExpr>(Arg)->getValue(); |
| auto Val = version::Version::parseCompilerVersionString( |
| Str, SourceLoc(), nullptr).getValue(); |
| auto thisVersion = version::Version::getCurrentCompilerVersion(); |
| return thisVersion >= Val; |
| } else if ((KindName == "swift") || (KindName == "compiler")) { |
| auto PUE = cast<PrefixUnaryExpr>(Arg); |
| auto Str = extractExprSource(Ctx.SourceMgr, PUE->getArg()); |
| auto Val = version::Version::parseVersionString( |
| Str, SourceLoc(), nullptr).getValue(); |
| if (KindName == "swift") { |
| return Ctx.LangOpts.EffectiveLanguageVersion >= Val; |
| } else if (KindName == "compiler") { |
| return version::Version::getCurrentLanguageVersion() >= Val; |
| } else { |
| llvm_unreachable("unsupported version conditional"); |
| } |
| } else if (KindName == "canImport") { |
| auto Str = extractExprSource(Ctx.SourceMgr, Arg); |
| return Ctx.canImportModule({ Ctx.getIdentifier(Str) , E->getLoc() }); |
| } |
| |
| auto Val = getDeclRefStr(Arg); |
| auto Kind = getPlatformConditionKind(KindName).getValue(); |
| return Ctx.LangOpts.checkPlatformCondition(Kind, 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<Expr *> 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<IsVersionIfConfigCondition, bool> { |
| |
| /// Get the identifier string from an \c Expr assuming it's an |
| /// \c UnresolvedDeclRefExpr. |
| StringRef getDeclRefStr(Expr *E) { |
| return cast<UnresolvedDeclRefExpr>(E)->getName().getBaseIdentifier().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" || |
| KindName == "compiler"; |
| } |
| |
| 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); |
| } |
| |
| /// Get the identifier string from an \c Expr if it's an |
| /// \c UnresolvedDeclRefExpr, otherwise the empty string. |
| static StringRef getDeclRefStr(Expr *E) { |
| if (auto *UDRE = dyn_cast<UnresolvedDeclRefExpr>(E)) { |
| return UDRE->getName().getBaseIdentifier().str(); |
| } |
| return ""; |
| } |
| |
| static bool isPlatformConditionDisjunction(Expr *E, PlatformConditionKind Kind, |
| ArrayRef<StringRef> Vals) { |
| if (auto *Or = dyn_cast<BinaryExpr>(E)) { |
| if (getDeclRefStr(Or->getFn()) == "||") { |
| auto Args = Or->getArg()->getElements(); |
| return (isPlatformConditionDisjunction(Args[0], Kind, Vals) && |
| isPlatformConditionDisjunction(Args[1], Kind, Vals)); |
| } |
| } else if (auto *P = dyn_cast<ParenExpr>(E)) { |
| return isPlatformConditionDisjunction(P->getSubExpr(), Kind, Vals); |
| } else if (auto *C = dyn_cast<CallExpr>(E)) { |
| if (getPlatformConditionKind(getDeclRefStr(C->getFn())) != Kind) |
| return false; |
| if (auto *ArgP = dyn_cast<ParenExpr>(C->getArg())) { |
| if (auto *Arg = ArgP->getSubExpr()) { |
| auto ArgStr = getDeclRefStr(Arg); |
| for (auto V : Vals) { |
| if (ArgStr == V) |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| // Search for the first occurrence of a _likely_ (but not definite) implicit |
| // simulator-environment platform condition, or negation thereof. This is |
| // defined as any logical conjunction of one or more os() platform conditions |
| // _strictly_ from the set {iOS, tvOS, watchOS} and one or more arch() platform |
| // conditions _strictly_ from the set {i386, x86_64}. |
| // |
| // These are (at the time of writing) defined as de-facto simulators in |
| // Platform.cpp, and if a user is testing them they're _likely_ looking for |
| // simulator-ness indirectly. If there is anything else in the condition aside |
| // from these conditions (or the negation of such a conjunction), we |
| // conservatively assume the user is testing something other than |
| // simulator-ness. |
| static Expr *findAnyLikelySimulatorEnvironmentTest(Expr *Condition) { |
| |
| if (!Condition) |
| return nullptr; |
| |
| if (auto *N = dyn_cast<PrefixUnaryExpr>(Condition)) { |
| return findAnyLikelySimulatorEnvironmentTest(N->getArg()); |
| } else if (auto *P = dyn_cast<ParenExpr>(Condition)) { |
| return findAnyLikelySimulatorEnvironmentTest(P->getSubExpr()); |
| } |
| |
| // We assume the user is writing the condition in CNF -- say (os(iOS) || |
| // os(tvOS)) && (arch(i386) || arch(x86_64)) -- rather than DNF, as the former |
| // is exponentially more terse, and these conditions are already quite |
| // unwieldy. If field evidence shows people using other variants, possibly add |
| // them here. |
| |
| auto isSimulatorPlatformOSTest = [](Expr *E) -> bool { |
| return isPlatformConditionDisjunction( |
| E, PlatformConditionKind::OS, {"iOS", "tvOS", "watchOS"}); |
| }; |
| |
| auto isSimulatorPlatformArchTest = [](Expr *E) -> bool { |
| return isPlatformConditionDisjunction( |
| E, PlatformConditionKind::Arch, {"i386", "x86_64"}); |
| }; |
| |
| if (auto *And = dyn_cast<BinaryExpr>(Condition)) { |
| if (getDeclRefStr(And->getFn()) == "&&") { |
| auto Args = And->getArg()->getElements(); |
| if ((isSimulatorPlatformOSTest(Args[0]) && |
| isSimulatorPlatformArchTest(Args[1])) || |
| (isSimulatorPlatformOSTest(Args[1]) && |
| isSimulatorPlatformArchTest(Args[0]))) { |
| return And; |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| } // end anonymous namespace |
| |
| |
| /// Parse and populate a #if ... #endif directive. |
| /// Delegate callback function to parse elements in the blocks. |
| ParserResult<IfConfigDecl> Parser::parseIfConfig( |
| llvm::function_ref<void(SmallVectorImpl<ASTNode> &, bool)> parseElements) { |
| SyntaxParsingContext IfConfigCtx(SyntaxContext, SyntaxKind::IfConfigDecl); |
| |
| SmallVector<IfConfigClause, 4> Clauses; |
| Parser::StructureMarkerRAII ParsingDecl( |
| *this, Tok.getLoc(), Parser::StructureMarkerKind::IfConfig); |
| |
| bool foundActive = false; |
| bool isVersionCondition = false; |
| while (1) { |
| SyntaxParsingContext ClauseContext(SyntaxContext, |
| SyntaxKind::IfConfigClause); |
| |
| bool isElse = Tok.is(tok::pound_else); |
| SourceLoc ClauseLoc = consumeToken(); |
| Expr *Condition = nullptr; |
| bool isActive = false; |
| |
| // Parse the condition. Evaluate it to determine the active |
| // clause unless we're doing a parse-only pass. |
| if (isElse) { |
| isActive = !foundActive && State->PerformConditionEvaluation; |
| } else { |
| llvm::SaveAndRestore<bool> S(InPoundIfEnvironment, true); |
| ParserResult<Expr> Result = parseExprSequence(diag::expected_expr, |
| /*isBasic*/true, |
| /*isForDirective*/true); |
| if (Result.isNull()) |
| return makeParserError(); |
| Condition = Result.get(); |
| if (validateIfConfigCondition(Condition, Context, Diags)) { |
| // Error in the condition; |
| isActive = false; |
| isVersionCondition = false; |
| } else if (!foundActive && State->PerformConditionEvaluation) { |
| // Evaluate the condition only if we haven't found any active one and |
| // we're not in parse-only mode. |
| isActive = evaluateIfConfigCondition(Condition, Context); |
| isVersionCondition = isVersionIfConfigCondition(Condition); |
| } |
| } |
| |
| foundActive |= isActive; |
| |
| if (!Tok.isAtStartOfLine() && Tok.isNot(tok::eof)) { |
| diagnose(Tok.getLoc(), |
| diag::extra_tokens_conditional_compilation_directive); |
| } |
| |
| if (Expr *Test = findAnyLikelySimulatorEnvironmentTest(Condition)) { |
| diagnose(Test->getLoc(), |
| diag::likely_simulator_platform_condition) |
| .fixItReplace(Test->getSourceRange(), |
| "targetEnvironment(simulator)"); |
| } |
| |
| // Parse elements |
| SmallVector<ASTNode, 16> Elements; |
| if (isActive || !isVersionCondition) { |
| parseElements(Elements, isActive); |
| } else { |
| DiagnosticTransaction DT(Diags); |
| skipUntilConditionalBlockClose(); |
| DT.abort(); |
| } |
| |
| Clauses.emplace_back(ClauseLoc, Condition, |
| Context.AllocateCopy(Elements), isActive); |
| |
| if (Tok.isNot(tok::pound_elseif, tok::pound_else)) |
| break; |
| |
| if (isElse) |
| diagnose(Tok, diag::expected_close_after_else_directive); |
| } |
| SyntaxContext->collectNodesInPlace(SyntaxKind::IfConfigClauseList); |
| |
| SourceLoc EndLoc; |
| bool HadMissingEnd = parseEndIfDirective(EndLoc); |
| |
| auto *ICD = new (Context) IfConfigDecl(CurDeclContext, |
| Context.AllocateCopy(Clauses), |
| EndLoc, HadMissingEnd); |
| return makeParserResult(ICD); |
| } |