From 29099ded0c1546dac891cf5510fd611a1d1992a5 Mon Sep 17 00:00:00 2001 From: Erik Pilkington Date: Sat, 16 Jul 2016 00:35:23 +0000 Subject: [PATCH] [ObjC] Implement @available in the Parser and AST This patch adds a new AST node: ObjCAvailabilityCheckExpr, and teaches the Parser and Sema to generate it. This node represents an availability check of the form: @available(macos 10.10, *); Which will eventually compile to a runtime check of the host's OS version. This is the first patch of the feature I proposed here: http://lists.llvm.org/pipermail/cfe-dev/2016-July/049851.html Differential Revision: https://reviews.llvm.org/D22171 llvm-svn: 275654 --- clang/include/clang-c/Index.h | 6 +- clang/include/clang/AST/Availability.h | 63 ++++++++++++ clang/include/clang/AST/ExprObjC.h | 47 ++++++++- clang/include/clang/AST/RecursiveASTVisitor.h | 1 + clang/include/clang/Basic/DiagnosticGroups.td | 1 + clang/include/clang/Basic/DiagnosticParseKinds.td | 15 +++ clang/include/clang/Basic/DiagnosticSemaKinds.td | 3 + clang/include/clang/Basic/StmtNodes.td | 1 + clang/include/clang/Basic/TokenKinds.def | 3 + clang/include/clang/Parse/Parser.h | 4 + clang/include/clang/Sema/Sema.h | 5 + clang/include/clang/Serialization/ASTBitCodes.h | 4 +- clang/lib/AST/Expr.cpp | 1 + clang/lib/AST/ExprClassification.cpp | 1 + clang/lib/AST/ExprConstant.cpp | 1 + clang/lib/AST/ItaniumMangle.cpp | 1 + clang/lib/AST/StmtPrinter.cpp | 5 + clang/lib/AST/StmtProfile.cpp | 5 + clang/lib/Parse/ParseDecl.cpp | 2 +- clang/lib/Parse/ParseExpr.cpp | 116 ++++++++++++++++++++++ clang/lib/Parse/ParseObjc.cpp | 2 + clang/lib/Sema/SemaExceptionSpec.cpp | 1 + clang/lib/Sema/SemaExpr.cpp | 24 +++++ clang/lib/Sema/TreeTransform.h | 6 ++ clang/lib/Serialization/ASTReaderStmt.cpp | 11 ++ clang/lib/Serialization/ASTWriterStmt.cpp | 7 ++ clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 1 + clang/test/Parser/objc-available.m | 24 +++++ clang/tools/libclang/CIndex.cpp | 2 + clang/tools/libclang/CXCursor.cpp | 6 +- 30 files changed, 364 insertions(+), 5 deletions(-) create mode 100644 clang/include/clang/AST/Availability.h create mode 100644 clang/test/Parser/objc-available.m diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h index ff3c86f..13db208 100644 --- a/clang/include/clang-c/Index.h +++ b/clang/include/clang-c/Index.h @@ -2014,7 +2014,11 @@ enum CXCursorKind { */ CXCursor_OMPArraySectionExpr = 147, - CXCursor_LastExpr = CXCursor_OMPArraySectionExpr, + /** \brief Represents an @available(...) check. + */ + CXCursor_ObjCAvailabilityCheckExpr = 148, + + CXCursor_LastExpr = CXCursor_ObjCAvailabilityCheckExpr, /* Statements */ CXCursor_FirstStmt = 200, diff --git a/clang/include/clang/AST/Availability.h b/clang/include/clang/AST/Availability.h new file mode 100644 index 0000000..5ed8313 --- /dev/null +++ b/clang/include/clang/AST/Availability.h @@ -0,0 +1,63 @@ +//===--- Availability.h - Classes for availability --------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This files defines some classes that implement availability checking. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_AVAILABILITY_H +#define LLVM_CLANG_AST_AVAILABILITY_H + +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/VersionTuple.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { + +/// \brief One specifier in an @available expression. +/// +/// \code +/// @available(macos 10.10, *) +/// \endcode +/// +/// Here, 'macos 10.10' and '*' both map to an instance of this type. +/// +class AvailabilitySpec { + /// Represents the version that this specifier requires. If the host OS + /// version is greater than or equal to Version, the @available will evaluate + /// to true. + VersionTuple Version; + + /// Name of the platform that Version corresponds to. + StringRef Platform; + + SourceLocation BeginLoc, EndLoc; + +public: + AvailabilitySpec(VersionTuple Version, StringRef Platform, + SourceLocation BeginLoc, SourceLocation EndLoc) + : Version(Version), Platform(Platform), BeginLoc(BeginLoc), + EndLoc(EndLoc) {} + + /// This constructor is used when representing the '*' case. + AvailabilitySpec(SourceLocation StarLoc) + : BeginLoc(StarLoc), EndLoc(StarLoc) {} + + VersionTuple getVersion() const { return Version; } + StringRef getPlatform() const { return Platform; } + SourceLocation getBeginLoc() const { return BeginLoc; } + SourceLocation getEndLoc() const { return EndLoc; } + + /// Returns true when this represents the '*' case. + bool isOtherPlatformSpec() const { return Version.empty(); } +}; + +} // end namespace clang + +#endif diff --git a/clang/include/clang/AST/ExprObjC.h b/clang/include/clang/AST/ExprObjC.h index 61e6383b..5f9623d 100644 --- a/clang/include/clang/AST/ExprObjC.h +++ b/clang/include/clang/AST/ExprObjC.h @@ -1562,7 +1562,52 @@ public: return T->getStmtClass() == ObjCBridgedCastExprClass; } }; - + +/// \brief A runtime availability query. +/// +/// There are 2 ways to spell this node: +/// \code +/// @available(macos 10.10, ios 8, *); // Objective-C +/// __builtin_available(macos 10.10, ios 8, *); // C, C++, and Objective-C +/// \endcode +/// +/// Note that we only need to keep track of one \c VersionTuple here, which is +/// the one that corresponds to the current deployment target. This is meant to +/// be used in the condition of an \c if, but it is also usable as top level +/// expressions. +/// +class ObjCAvailabilityCheckExpr : public Expr { + VersionTuple VersionToCheck; + SourceLocation AtLoc, RParen; + + friend class ASTStmtReader; +public: + ObjCAvailabilityCheckExpr(VersionTuple VersionToCheck, SourceLocation AtLoc, + SourceLocation RParen, QualType Ty) + : Expr(ObjCAvailabilityCheckExprClass, Ty, VK_RValue, OK_Ordinary, false, + false, false, false), + VersionToCheck(VersionToCheck), AtLoc(AtLoc), RParen(RParen) {} + + explicit ObjCAvailabilityCheckExpr(EmptyShell Shell) + : Expr(ObjCAvailabilityCheckExprClass, Shell) {} + + SourceLocation getLocStart() const { return AtLoc; } + SourceLocation getLocEnd() const { return RParen; } + SourceRange getSourceRange() const { return {AtLoc, RParen}; } + + /// \brief This may be '*', in which case this should fold to true. + bool hasVersion() const { return !VersionToCheck.empty(); } + VersionTuple getVersion() { return VersionToCheck; } + + child_range children() { + return child_range(child_iterator(), child_iterator()); + } + + static bool classof(const Stmt *T) { + return T->getStmtClass() == ObjCAvailabilityCheckExprClass; + } +}; + } // end namespace clang #endif diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index 94637a5..f918b83 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2352,6 +2352,7 @@ DEF_TRAVERSE_STMT(ObjCIndirectCopyRestoreExpr, {}) DEF_TRAVERSE_STMT(ObjCBridgedCastExpr, { TRY_TO(TraverseTypeLoc(S->getTypeInfoAsWritten()->getTypeLoc())); }) +DEF_TRAVERSE_STMT(ObjCAvailabilityCheckExpr, {}) DEF_TRAVERSE_STMT(ParenExpr, {}) DEF_TRAVERSE_STMT(ParenListExpr, {}) DEF_TRAVERSE_STMT(PredefinedExpr, {}) diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 979c81f..72dfedb 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -96,6 +96,7 @@ def DeprecatedAttributes : DiagGroup<"deprecated-attributes">; def DeprecatedDeclarations : DiagGroup<"deprecated-declarations">; def UnavailableDeclarations : DiagGroup<"unavailable-declarations">; def PartialAvailability : DiagGroup<"partial-availability">; +def UnguardedAvailability : DiagGroup<"unguarded-availability">; def DeprecatedImplementations :DiagGroup<"deprecated-implementations">; def DeprecatedIncrementBool : DiagGroup<"deprecated-increment-bool">; def DeprecatedRegister : DiagGroup<"deprecated-register">; diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index e01d946..e5c6468 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -823,6 +823,21 @@ def warn_availability_and_unavailable : Warning< "'unavailable' availability overrides all other availability information">, InGroup; +// @available(...) +def err_avail_query_expected_condition : Error< + "expected an availability condition here">; +def err_avail_query_expected_platform_name : Error< + "expected a platform name here">; + +def err_avail_query_unrecognized_platform_name : Error< + "unrecognized platform name %0">; +def err_availability_query_wildcard_required: Error< + "must handle potential future platforms with '*'">; +def err_availability_query_repeated_platform: Error< + "version for '%0' already specified">; +def err_availability_query_repeated_star : Error< + "'*' query has already been specified">; + // Type safety attributes def err_type_safety_unknown_flag : Error< "invalid comparison flag %0; use 'layout_compatible' or 'must_be_null'">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 45e77d8..1203fe76 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2599,6 +2599,9 @@ def note_overridden_method : Note< def note_protocol_method : Note< "protocol method is here">; +def warn_available_using_star_case : Warning< + "using '*' case here, platform %0 is not accounted for">, InGroup; + // Thread Safety Attributes def warn_invalid_capability_name : Warning< "invalid capability name '%0'; capability name must be 'mutex' or 'role'">, diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td index df7e736..973b3bb 100644 --- a/clang/include/clang/Basic/StmtNodes.td +++ b/clang/include/clang/Basic/StmtNodes.td @@ -165,6 +165,7 @@ def ObjCIsaExpr : DStmt; def ObjCIndirectCopyRestoreExpr : DStmt; def ObjCBoolLiteralExpr : DStmt; def ObjCSubscriptRefExpr : DStmt; +def ObjCAvailabilityCheckExpr : DStmt; // Obj-C ARC Expressions. def ObjCBridgedCastExpr : DStmt; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index a08d280..882c6e4 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -630,6 +630,8 @@ KEYWORD(__builtin_convertvector , KEYALL) ALIAS("__char16_t" , char16_t , KEYCXX) ALIAS("__char32_t" , char32_t , KEYCXX) +KEYWORD(__builtin_available , KEYALL) + // Clang-specific keywords enabled only in testing. TESTING_KEYWORD(__unknown_anytype , KEYALL) @@ -668,6 +670,7 @@ OBJC2_AT_KEYWORD(optional) OBJC2_AT_KEYWORD(synthesize) OBJC2_AT_KEYWORD(dynamic) OBJC2_AT_KEYWORD(import) +OBJC2_AT_KEYWORD(available) // TODO: What to do about context-sensitive keywords like: // bycopy/byref/in/inout/oneway/out? diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 9d59f25..5838a44 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -14,6 +14,7 @@ #ifndef LLVM_CLANG_PARSE_PARSER_H #define LLVM_CLANG_PARSE_PARSER_H +#include "clang/AST/Availability.h" #include "clang/Basic/OpenMPKinds.h" #include "clang/Basic/OperatorPrecedence.h" #include "clang/Basic/Specifiers.h" @@ -2244,6 +2245,9 @@ private: SourceLocation ScopeLoc, AttributeList::Syntax Syntax); + Optional ParseAvailabilitySpec(); + ExprResult ParseAvailabilityCheckExpr(SourceLocation StartLoc); + void ParseObjCBridgeRelatedAttribute(IdentifierInfo &ObjCBridgeRelated, SourceLocation ObjCBridgeRelatedLoc, ParsedAttributes &attrs, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 4f2f730..25aa805 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -16,6 +16,7 @@ #define LLVM_CLANG_SEMA_SEMA_H #include "clang/AST/Attr.h" +#include "clang/AST/Availability.h" #include "clang/AST/DeclarationName.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" @@ -4739,6 +4740,10 @@ public: /// ActOnObjCBoolLiteral - Parse {__objc_yes,__objc_no} literals. ExprResult ActOnObjCBoolLiteral(SourceLocation OpLoc, tok::TokenKind Kind); + ExprResult + ActOnObjCAvailabilityCheckExpr(llvm::ArrayRef AvailSpecs, + SourceLocation AtLoc, SourceLocation RParen); + /// ActOnCXXNullPtrLiteral - Parse 'nullptr'. ExprResult ActOnCXXNullPtrLiteral(SourceLocation Loc); diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index 809dbc8..79c6a06 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -1349,8 +1349,10 @@ namespace clang { STMT_OBJC_AT_THROW, /// \brief An ObjCAutoreleasePoolStmt record. STMT_OBJC_AUTORELEASE_POOL, - /// \brief A ObjCBoolLiteralExpr record. + /// \brief An ObjCBoolLiteralExpr record. EXPR_OBJC_BOOL_LITERAL, + /// \brief An ObjCAvailabilityCheckExpr record. + EXPR_OBJC_AVAILABILITY_CHECK, // C++ diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index bd4cf39..091e878 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -2856,6 +2856,7 @@ bool Expr::HasSideEffects(const ASTContext &Ctx, case ObjCStringLiteralClass: case ObjCEncodeExprClass: case ObjCBoolLiteralExprClass: + case ObjCAvailabilityCheckExprClass: case CXXUuidofExprClass: case OpaqueValueExprClass: // These never have a side-effect. diff --git a/clang/lib/AST/ExprClassification.cpp b/clang/lib/AST/ExprClassification.cpp index 642cdd1..89cc9bc 100644 --- a/clang/lib/AST/ExprClassification.cpp +++ b/clang/lib/AST/ExprClassification.cpp @@ -178,6 +178,7 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) { case Expr::ObjCArrayLiteralClass: case Expr::ObjCDictionaryLiteralClass: case Expr::ObjCBoolLiteralExprClass: + case Expr::ObjCAvailabilityCheckExprClass: case Expr::ParenListExprClass: case Expr::SizeOfPackExprClass: case Expr::SubstNonTypeTemplateParmPackExprClass: diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 28bdf6d..a25a6c7 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -9402,6 +9402,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) { case Expr::ObjCPropertyRefExprClass: case Expr::ObjCSubscriptRefExprClass: case Expr::ObjCIsaExprClass: + case Expr::ObjCAvailabilityCheckExprClass: case Expr::ShuffleVectorExprClass: case Expr::ConvertVectorExprClass: case Expr::BlockExprClass: diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp index 5a7c455..694fde3 100644 --- a/clang/lib/AST/ItaniumMangle.cpp +++ b/clang/lib/AST/ItaniumMangle.cpp @@ -3239,6 +3239,7 @@ recurse: case Expr::ObjCDictionaryLiteralClass: case Expr::ObjCSubscriptRefExprClass: case Expr::ObjCIndirectCopyRestoreExprClass: + case Expr::ObjCAvailabilityCheckExprClass: case Expr::OffsetOfExprClass: case Expr::PredefinedExprClass: case Expr::ShuffleVectorExprClass: diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index 64d9a329..8797a13 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -497,6 +497,11 @@ void StmtPrinter::VisitObjCAtThrowStmt(ObjCAtThrowStmt *Node) { OS << ";\n"; } +void StmtPrinter::VisitObjCAvailabilityCheckExpr( + ObjCAvailabilityCheckExpr *Node) { + OS << "@available(...)"; +} + void StmtPrinter::VisitObjCAtSynchronizedStmt(ObjCAtSynchronizedStmt *Node) { Indent() << "@synchronized ("; PrintExpr(Node->getSynchExpr()); diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp index 5ce6e5e..0a39413 100644 --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -1632,6 +1632,11 @@ void StmtProfiler::VisitObjCBridgedCastExpr(const ObjCBridgedCastExpr *S) { ID.AddBoolean(S->getBridgeKind()); } +void StmtProfiler::VisitObjCAvailabilityCheckExpr( + const ObjCAvailabilityCheckExpr *S) { + VisitExpr(S); +} + void StmtProfiler::VisitDecl(const Decl *D) { ID.AddInteger(D? D->getKind() : 0); diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 9a754ee..45e1c3e 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -720,7 +720,7 @@ static bool VersionNumberSeparator(const char Separator) { /// simple-integer ',' simple-integer /// simple-integer ',' simple-integer ',' simple-integer VersionTuple Parser::ParseVersionTuple(SourceRange &Range) { - Range = Tok.getLocation(); + Range = SourceRange(Tok.getLocation(), Tok.getEndLoc()); if (!Tok.is(tok::numeric_constant)) { Diag(Tok, diag::err_expected_version); diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 4e39d42..47d1326 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1009,6 +1009,8 @@ ExprResult Parser::ParseCastExpression(bool isUnaryExpression, case tok::kw__Generic: // primary-expression: generic-selection [C11 6.5.1] Res = ParseGenericSelectionExpression(); break; + case tok::kw___builtin_available: + return ParseAvailabilityCheckExpr(Tok.getLocation()); case tok::kw___builtin_va_arg: case tok::kw___builtin_offsetof: case tok::kw___builtin_choose_expr: @@ -2869,3 +2871,117 @@ ExprResult Parser::ParseObjCBoolLiteral() { tok::TokenKind Kind = Tok.getKind(); return Actions.ActOnObjCBoolLiteral(ConsumeToken(), Kind); } + +/// Validate availability spec list, emitting diagnostics if necessary. Returns +/// true if invalid. +static bool CheckAvailabilitySpecList(Parser &P, + ArrayRef AvailSpecs) { + llvm::SmallSet Platforms; + bool HasOtherPlatformSpec = false; + bool Valid = true; + for (const auto &Spec : AvailSpecs) { + if (Spec.isOtherPlatformSpec()) { + if (HasOtherPlatformSpec) { + P.Diag(Spec.getBeginLoc(), diag::err_availability_query_repeated_star); + Valid = false; + } + + HasOtherPlatformSpec = true; + continue; + } + + bool Inserted = Platforms.insert(Spec.getPlatform()).second; + if (!Inserted) { + // Rule out multiple version specs referring to the same platform. + // For example, we emit an error for: + // @available(macos 10.10, macos 10.11, *) + StringRef Platform = Spec.getPlatform(); + P.Diag(Spec.getBeginLoc(), diag::err_availability_query_repeated_platform) + << Spec.getEndLoc() << Platform; + Valid = false; + } + } + + if (!HasOtherPlatformSpec) { + SourceLocation InsertWildcardLoc = AvailSpecs.back().getEndLoc(); + P.Diag(InsertWildcardLoc, diag::err_availability_query_wildcard_required) + << FixItHint::CreateInsertion(InsertWildcardLoc, ", *"); + return true; + } + + return !Valid; +} + +/// Parse availability query specification. +/// +/// availability-spec: +/// '*' +/// identifier version-tuple +Optional Parser::ParseAvailabilitySpec() { + if (Tok.is(tok::star)) { + return AvailabilitySpec(ConsumeToken()); + } else { + // Parse the platform name. + if (Tok.isNot(tok::identifier)) { + Diag(Tok, diag::err_avail_query_expected_platform_name); + return None; + } + + IdentifierLoc *PlatformIdentifier = ParseIdentifierLoc(); + SourceRange VersionRange; + VersionTuple Version = ParseVersionTuple(VersionRange); + + if (Version.empty()) + return None; + + StringRef Platform = PlatformIdentifier->Ident->getName(); + + if (AvailabilityAttr::getPrettyPlatformName(Platform).empty()) { + Diag(PlatformIdentifier->Loc, + diag::err_avail_query_unrecognized_platform_name) + << Platform; + return None; + } + + return AvailabilitySpec(Version, Platform, PlatformIdentifier->Loc, + VersionRange.getEnd()); + } +} + +ExprResult Parser::ParseAvailabilityCheckExpr(SourceLocation BeginLoc) { + assert(Tok.is(tok::kw___builtin_available) || + Tok.isObjCAtKeyword(tok::objc_available)); + + // Eat the available or __builtin_available. + ConsumeToken(); + + BalancedDelimiterTracker Parens(*this, tok::l_paren); + if (Parens.expectAndConsume()) + return ExprError(); + + SmallVector AvailSpecs; + bool HasError = false; + while (true) { + Optional Spec = ParseAvailabilitySpec(); + if (!Spec) + HasError = true; + else + AvailSpecs.push_back(*Spec); + + if (!TryConsumeToken(tok::comma)) + break; + } + + if (HasError) { + SkipUntil(tok::r_paren, StopAtSemi); + return ExprError(); + } + + CheckAvailabilitySpecList(*this, AvailSpecs); + + if (Parens.consumeClose()) + return ExprError(); + + return Actions.ActOnObjCAvailabilityCheckExpr(AvailSpecs, BeginLoc, + Parens.getCloseLocation()); +} diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp index 22c8326..67abe58 100644 --- a/clang/lib/Parse/ParseObjc.cpp +++ b/clang/lib/Parse/ParseObjc.cpp @@ -2858,6 +2858,8 @@ ExprResult Parser::ParseObjCAtExpression(SourceLocation AtLoc) { return ParsePostfixExpressionSuffix(ParseObjCProtocolExpression(AtLoc)); case tok::objc_selector: return ParsePostfixExpressionSuffix(ParseObjCSelectorExpression(AtLoc)); + case tok::objc_available: + return ParseAvailabilityCheckExpr(AtLoc); default: { const char *str = nullptr; if (GetLookAheadToken(1).is(tok::l_brace)) { diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index 61ea87a..4a21eb3 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -1146,6 +1146,7 @@ CanThrowResult Sema::canThrow(const Expr *E) { case Expr::ObjCIndirectCopyRestoreExprClass: case Expr::ObjCProtocolExprClass: case Expr::ObjCSelectorExprClass: + case Expr::ObjCAvailabilityCheckExprClass: case Expr::OffsetOfExprClass: case Expr::PackExpansionExprClass: case Expr::PseudoObjectExprClass: diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 0c8a913..57159fb 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -15083,3 +15083,27 @@ Sema::ActOnObjCBoolLiteral(SourceLocation OpLoc, tok::TokenKind Kind) { return new (Context) ObjCBoolLiteralExpr(Kind == tok::kw___objc_yes, BoolT, OpLoc); } + +ExprResult Sema::ActOnObjCAvailabilityCheckExpr( + llvm::ArrayRef AvailSpecs, SourceLocation AtLoc, + SourceLocation RParen) { + + StringRef Platform = getASTContext().getTargetInfo().getPlatformName(); + + auto Spec = std::find_if(AvailSpecs.begin(), AvailSpecs.end(), + [&](const AvailabilitySpec &Spec) { + return Spec.getPlatform() == Platform; + }); + + VersionTuple Version; + if (Spec != AvailSpecs.end()) + Version = Spec->getVersion(); + else + // This is the '*' case in @available. We should diagnose this; the + // programmer should explicitly account for this case if they target this + // platform. + Diag(AtLoc, diag::warn_available_using_star_case) << RParen << Platform; + + return new (Context) + ObjCAvailabilityCheckExpr(Version, AtLoc, RParen, Context.BoolTy); +} diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 0dbdebf..36859f6 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -11112,6 +11112,12 @@ TransformObjCBridgedCastExpr(ObjCBridgedCastExpr *E) { Result.get()); } +template +ExprResult TreeTransform::TransformObjCAvailabilityCheckExpr( + ObjCAvailabilityCheckExpr *E) { + return E; +} + template ExprResult TreeTransform::TransformObjCMessageExpr(ObjCMessageExpr *E) { diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 2fa3a44..395da42 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -1182,6 +1182,14 @@ void ASTStmtReader::VisitObjCBoolLiteralExpr(ObjCBoolLiteralExpr *E) { E->setLocation(ReadSourceLocation(Record, Idx)); } +void ASTStmtReader::VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) { + VisitExpr(E); + SourceRange R = Reader.ReadSourceRange(F, Record, Idx); + E->AtLoc = R.getBegin(); + E->RParen = R.getEnd(); + E->VersionToCheck = Reader.ReadVersionTuple(Record, Idx); +} + //===----------------------------------------------------------------------===// // C++ Expressions and Statements //===----------------------------------------------------------------------===// @@ -3221,6 +3229,9 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) { case EXPR_OBJC_BOOL_LITERAL: S = new (Context) ObjCBoolLiteralExpr(Empty); break; + case EXPR_OBJC_AVAILABILITY_CHECK: + S = new (Context) ObjCAvailabilityCheckExpr(Empty); + break; case STMT_SEH_LEAVE: S = new (Context) SEHLeaveStmt(Empty); break; diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 73f7219..84e718e 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -1142,6 +1142,13 @@ void ASTStmtWriter::VisitObjCBoolLiteralExpr(ObjCBoolLiteralExpr *E) { Code = serialization::EXPR_OBJC_BOOL_LITERAL; } +void ASTStmtWriter::VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) { + VisitExpr(E); + Record.AddSourceRange(E->getSourceRange()); + Record.AddVersionTuple(E->getVersion()); + Code = serialization::EXPR_OBJC_AVAILABILITY_CHECK; +} + //===----------------------------------------------------------------------===// // C++ Expressions and Statements. //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index baa03e0..405aecd 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -916,6 +916,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::CXXScalarValueInitExprClass: case Stmt::CXXBoolLiteralExprClass: case Stmt::ObjCBoolLiteralExprClass: + case Stmt::ObjCAvailabilityCheckExprClass: case Stmt::FloatingLiteralClass: case Stmt::NoInitExprClass: case Stmt::SizeOfPackExprClass: diff --git a/clang/test/Parser/objc-available.m b/clang/test/Parser/objc-available.m new file mode 100644 index 0000000..ca30753 --- /dev/null +++ b/clang/test/Parser/objc-available.m @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -fsyntax-only -Wunguarded-availability -triple x86_64-apple-macosx10.10.0 -verify %s + +void f() { + + if (@available(macos 10.12, *)) {} + else if (@available(macos 10.11, *)) {} + else {} + + (void)__builtin_available(ios 8, macos 10.10, *); + + (void)@available(macos 10.11); // expected-error{{must handle potential future platforms with '*'}} + (void)@available(macos 10.11, macos 10.11, *); // expected-error{{version for 'macos' already specified}} + + (void)@available(erik_os 10.11, *); // expected-error{{unrecognized platform name erik_os}} + + (void)@available(erik_os 10.10, hat_os 1.0, *); // expected-error 2 {{unrecognized platform name}} + + (void)@available(ios 8, *); // expected-warning{{using '*' case here, platform macos is not accounted for}} + + (void)@available(); // expected-error{{expected a platform name here}} + (void)@available(macos 10.10,); // expected-error{{expected a platform name here}} + (void)@available(macos); // expected-error{{expected a version}} + (void)@available; // expected-error{{expected '('}} +} diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp index f4f9c16..027bf95 100644 --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -4611,6 +4611,8 @@ CXString clang_getCursorKindSpelling(enum CXCursorKind Kind) { return cxstring::createRef("ObjCStringLiteral"); case CXCursor_ObjCBoolLiteralExpr: return cxstring::createRef("ObjCBoolLiteralExpr"); + case CXCursor_ObjCAvailabilityCheckExpr: + return cxstring::createRef("ObjCAvailabilityCheckExpr"); case CXCursor_ObjCSelfExpr: return cxstring::createRef("ObjCSelfExpr"); case CXCursor_ObjCEncodeExpr: diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp index 80bb569..19ab0f9 100644 --- a/clang/tools/libclang/CXCursor.cpp +++ b/clang/tools/libclang/CXCursor.cpp @@ -447,7 +447,11 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent, case Stmt::ObjCBoolLiteralExprClass: K = CXCursor_ObjCBoolLiteralExpr; break; - + + case Stmt::ObjCAvailabilityCheckExprClass: + K = CXCursor_ObjCAvailabilityCheckExpr; + break; + case Stmt::ObjCBridgedCastExprClass: K = CXCursor_ObjCBridgedCastExpr; break; -- 2.7.4