| //===--- ParseDecl.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/Basic/Defer.h" |
| #include "swift/Basic/LangOptions.h" |
| #include "swift/Basic/Version.h" |
| #include "swift/Parse/Lexer.h" |
| #include "llvm/ADT/StringSwitch.h" |
| #include "llvm/Support/Compiler.h" |
| #include "llvm/Support/SaveAndRestore.h" |
| |
| using namespace swift; |
| |
| /// Parse and populate a list of #if/#elseif/#else/#endif clauses. |
| /// Delegate callback function to parse elements in the blocks. |
| template <typename ElemTy, unsigned N> |
| static ParserStatus parseIfConfig( |
| Parser &P, SmallVectorImpl<IfConfigClause<ElemTy>> &Clauses, |
| SourceLoc &EndLoc, bool HadMissingEnd, |
| llvm::function_ref<void(SmallVectorImpl<ElemTy> &, bool)> parseElements) { |
| Parser::StructureMarkerRAII ParsingDecl( |
| P, P.Tok.getLoc(), Parser::StructureMarkerKind::IfConfig); |
| |
| bool foundActive = false; |
| ConditionalCompilationExprState ConfigState; |
| while (1) { |
| bool isElse = P.Tok.is(tok::pound_else); |
| SourceLoc ClauseLoc = P.consumeToken(); |
| Expr *Condition = nullptr; |
| |
| // Parse and evaluate the directive. |
| if (isElse) { |
| ConfigState.setConditionActive(!foundActive); |
| } else { |
| llvm::SaveAndRestore<bool> S(P.InPoundIfEnvironment, true); |
| ParserResult<Expr> Result = P.parseExprSequence(diag::expected_expr, |
| /*isBasic*/true, |
| /*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); |
| } |
| |
| foundActive |= ConfigState.isConditionActive(); |
| |
| if (!P.Tok.isAtStartOfLine() && P.Tok.isNot(tok::eof)) { |
| P.diagnose(P.Tok.getLoc(), |
| diag::extra_tokens_conditional_compilation_directive); |
| } |
| |
| // Parse elements |
| SmallVector<ElemTy, N> Elements; |
| if (ConfigState.shouldParse()) { |
| parseElements(Elements, ConfigState.isConditionActive()); |
| } else { |
| DiagnosticTransaction DT(P.Diags); |
| P.skipUntilConditionalBlockClose(); |
| DT.abort(); |
| } |
| |
| Clauses.push_back(IfConfigClause<ElemTy>(ClauseLoc, Condition, |
| P.Context.AllocateCopy(Elements), |
| ConfigState.isConditionActive())); |
| |
| if (P.Tok.isNot(tok::pound_elseif, tok::pound_else)) |
| break; |
| |
| if (isElse) |
| P.diagnose(P.Tok, diag::expected_close_after_else_directive); |
| } |
| |
| HadMissingEnd = P.parseEndIfDirective(EndLoc); |
| return makeParserSuccess(); |
| } |
| |
| ParserResult<IfConfigDecl> Parser::parseDeclIfConfig(ParseDeclOptions Flags) { |
| SmallVector<IfConfigClause<Decl *>, 4> Clauses; |
| SourceLoc EndLoc; |
| bool HadMissingEnd = false; |
| auto Status = parseIfConfig<Decl *, 8>( |
| *this, Clauses, EndLoc, HadMissingEnd, |
| [&](SmallVectorImpl<Decl *> &Decls, bool IsActive) { |
| Optional<Scope> scope; |
| if (!IsActive) |
| scope.emplace(this, getScopeInfo().getCurrentScope()->getKind(), |
| /*inactiveConfigBlock=*/true); |
| |
| ParserStatus Status; |
| bool PreviousHadSemi = true; |
| while (Tok.isNot(tok::pound_else, tok::pound_endif, tok::pound_elseif, |
| tok::eof)) { |
| if (Tok.is(tok::r_brace)) { |
| diagnose(Tok.getLoc(), |
| diag::unexpected_rbrace_in_conditional_compilation_block); |
| // If we see '}', following declarations don't look like belong to |
| // the current decl context; skip them. |
| skipUntilConditionalBlockClose(); |
| break; |
| } |
| Status |= parseDeclItem(PreviousHadSemi, Flags, |
| [&](Decl *D) {Decls.push_back(D);}); |
| } |
| }); |
| if (Status.isError()) |
| return makeParserErrorResult<IfConfigDecl>(); |
| |
| IfConfigDecl *ICD = new (Context) IfConfigDecl(CurDeclContext, |
| Context.AllocateCopy(Clauses), |
| EndLoc, HadMissingEnd); |
| return makeParserResult(ICD); |
| } |
| |
| ParserResult<Stmt> Parser::parseStmtIfConfig(BraceItemListKind Kind) { |
| SmallVector<IfConfigClause<ASTNode>, 4> Clauses; |
| SourceLoc EndLoc; |
| bool HadMissingEnd = false; |
| auto Status = parseIfConfig<ASTNode, 16>( |
| *this, Clauses, EndLoc, HadMissingEnd, |
| [&](SmallVectorImpl<ASTNode> &Elements, bool IsActive) { |
| parseBraceItems(Elements, Kind, IsActive |
| ? BraceItemListKind::ActiveConditionalBlock |
| : BraceItemListKind::InactiveConditionalBlock); |
| }); |
| if (Status.isError()) |
| return makeParserErrorResult<Stmt>(); |
| |
| auto *ICS = new (Context) IfConfigStmt(Context.AllocateCopy(Clauses), |
| 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<ParenExpr>(condition)) |
| return classifyConditionalCompilationExpr(PE->getSubExpr(), Context, D); |
| |
| // Evaluate a "&&" or "||" expression. |
| if (auto *SE = dyn_cast<SequenceExpr>(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<UnresolvedDeclRefExpr>(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<UnresolvedDeclRefExpr>(condition)) { |
| auto name = UDRE->getName().getBaseName().str(); |
| return {Context.LangOpts.isCustomConditionalCompilationFlagSet(name), |
| ConditionalCompilationExprKind::DeclRef}; |
| } |
| |
| // Evaluate a Boolean literal. |
| if (auto *boolLit = dyn_cast<BooleanLiteralExpr>(condition)) { |
| return {boolLit->getValue(), ConditionalCompilationExprKind::Boolean}; |
| } |
| |
| // Evaluate a negation (unary "!") expression. |
| if (auto *PUE = dyn_cast<PrefixUnaryExpr>(condition)) { |
| // If the PUE is not a negation expression, return false |
| auto name = |
| cast<UnresolvedDeclRefExpr>(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<CallExpr>(condition)) { |
| // look up target config, and compare value |
| auto fnNameExpr = dyn_cast<UnresolvedDeclRefExpr>(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<ParenExpr>(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<StringLiteralExpr>(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<PrefixUnaryExpr>(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<UnresolvedDeclRefExpr>(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<UnresolvedDeclRefExpr>(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<PlatformConditionKind>(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<StringRef> 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<IntegerLiteralExpr>(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(); |
| } |