Index: include/clang/AST/DeclBase.h =================================================================== --- include/clang/AST/DeclBase.h +++ include/clang/AST/DeclBase.h @@ -628,11 +628,10 @@ /// describing why the declaration has not been introduced, is /// deprecated, or is unavailable. /// - /// \param EnclosingVersion The version to compare with. If empty, assume the - /// deployment target version. - AvailabilityResult - getAvailability(std::string *Message = nullptr, - VersionTuple EnclosingVersion = VersionTuple()) const; + /// \param Attribute The attribute that lead to this result. One of + /// UnavailableAttr, DeprecatedAttr, AvailabilityAttr, or nullptr. + AvailabilityResult getAvailability(std::string *Message = nullptr, + const Attr **Attribute = nullptr) const; /// \brief Retrieve the version of the target platform in which this /// declaration was introduced. Index: include/clang/Sema/DelayedDiagnostic.h =================================================================== --- include/clang/Sema/DelayedDiagnostic.h +++ include/clang/Sema/DelayedDiagnostic.h @@ -126,6 +126,7 @@ SourceLocation Loc, const NamedDecl *ReferringDecl, const NamedDecl *OffendingDecl, + const Attr *OffendingAttr, const ObjCInterfaceDecl *UnknownObjCClass, const ObjCPropertyDecl *ObjCProperty, StringRef Msg, @@ -215,11 +216,16 @@ return AvailabilityData.ObjCPropertyAccess; } + const Attr *getAvailabilityAttr() const { + return AvailabilityData.OffendingAttr; + } + private: struct AD { const NamedDecl *ReferringDecl; const NamedDecl *OffendingDecl; + const Attr *OffendingAttr; const ObjCInterfaceDecl *UnknownObjCClass; const ObjCPropertyDecl *ObjCProperty; const char *Message; Index: lib/AST/DeclBase.cpp =================================================================== --- lib/AST/DeclBase.cpp +++ lib/AST/DeclBase.cpp @@ -474,11 +474,9 @@ /// diagnostics. static AvailabilityResult CheckAvailability(ASTContext &Context, const AvailabilityAttr *A, - std::string *Message, - VersionTuple EnclosingVersion) { - if (EnclosingVersion.empty()) - EnclosingVersion = Context.getTargetInfo().getPlatformMinVersion(); - + std::string *Message) { + VersionTuple EnclosingVersion = + Context.getTargetInfo().getPlatformMinVersion(); if (EnclosingVersion.empty()) return AR_Available; @@ -560,11 +558,12 @@ } AvailabilityResult Decl::getAvailability(std::string *Message, - VersionTuple EnclosingVersion) const { + const Attr **Attribute) const { if (auto *FTD = dyn_cast(this)) - return FTD->getTemplatedDecl()->getAvailability(Message, EnclosingVersion); + return FTD->getTemplatedDecl()->getAvailability(Message, Attribute); AvailabilityResult Result = AR_Available; + const Attr *CorrispondingAttr = nullptr; std::string ResultMessage; for (const auto *A : attrs()) { @@ -574,7 +573,7 @@ if (Message) ResultMessage = Deprecated->getMessage(); - + CorrispondingAttr = Deprecated; Result = AR_Deprecated; continue; } @@ -582,18 +581,24 @@ if (const auto *Unavailable = dyn_cast(A)) { if (Message) *Message = Unavailable->getMessage(); + if (Attribute) + *Attribute = Unavailable; return AR_Unavailable; } if (const auto *Availability = dyn_cast(A)) { - AvailabilityResult AR = CheckAvailability(getASTContext(), Availability, - Message, EnclosingVersion); + AvailabilityResult AR = + CheckAvailability(getASTContext(), Availability, Message); - if (AR == AR_Unavailable) + if (AR == AR_Unavailable) { + if (Attribute) + *Attribute = Availability; return AR_Unavailable; + } if (AR > Result) { Result = AR; + CorrispondingAttr = Availability; if (Message) ResultMessage.swap(*Message); } @@ -603,6 +608,8 @@ if (Message) Message->swap(ResultMessage); + if (Attribute) + *Attribute = CorrispondingAttr; return Result; } @@ -660,8 +667,8 @@ return true; if (const auto *Availability = dyn_cast(A)) { - if (CheckAvailability(getASTContext(), Availability, nullptr, - VersionTuple()) == AR_NotYetIntroduced) + if (CheckAvailability(getASTContext(), Availability, nullptr) == + AR_NotYetIntroduced) return true; } } Index: lib/Sema/DelayedDiagnostic.cpp =================================================================== --- lib/Sema/DelayedDiagnostic.cpp +++ lib/Sema/DelayedDiagnostic.cpp @@ -24,6 +24,7 @@ SourceLocation Loc, const NamedDecl *ReferringDecl, const NamedDecl *OffendingDecl, + const Attr *OffendingAttr, const ObjCInterfaceDecl *UnknownObjCClass, const ObjCPropertyDecl *ObjCProperty, StringRef Msg, @@ -34,6 +35,7 @@ DD.Loc = Loc; DD.AvailabilityData.ReferringDecl = ReferringDecl; DD.AvailabilityData.OffendingDecl = OffendingDecl; + DD.AvailabilityData.OffendingAttr = OffendingAttr; DD.AvailabilityData.UnknownObjCClass = UnknownObjCClass; DD.AvailabilityData.ObjCProperty = ObjCProperty; char *MessageData = nullptr; Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -6854,43 +6854,16 @@ diag.Triggered = true; } -static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context, - const Decl *D) { - // Check each AvailabilityAttr to find the one for this platform. - for (const auto *A : D->attrs()) { - if (const auto *Avail = dyn_cast(A)) { - // FIXME: this is copied from CheckAvailability. We should try to - // de-duplicate. - - // Check if this is an App Extension "platform", and if so chop off - // the suffix for matching with the actual platform. - StringRef ActualPlatform = Avail->getPlatform()->getName(); - StringRef RealizedPlatform = ActualPlatform; - if (Context.getLangOpts().AppExt) { - size_t suffix = RealizedPlatform.rfind("_app_extension"); - if (suffix != StringRef::npos) - RealizedPlatform = RealizedPlatform.slice(0, suffix); - } - - StringRef TargetPlatform = Context.getTargetInfo().getPlatformName(); - - // Match the platform name. - if (RealizedPlatform == TargetPlatform) - return Avail; - } - } - return nullptr; -} - /// The diagnostic we should emit for \c D, and the declaration that /// originated it, or \c AR_Available. /// /// \param D The declaration to check. /// \param Message If non-null, this will be populated with the message from /// the availability attribute that is selected. -static std::pair +static std::tuple ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message) { - AvailabilityResult Result = D->getAvailability(Message); + const Attr *Attribute = nullptr; + AvailabilityResult Result = D->getAvailability(Message, &Attribute); // For typedefs, if the typedef declaration appears available look // to the underlying type to see if it is more restrictive. @@ -6898,7 +6871,7 @@ if (Result == AR_Available) { if (const TagType *TT = TD->getUnderlyingType()->getAs()) { D = TT->getDecl(); - Result = D->getAvailability(Message); + Result = D->getAvailability(Message, &Attribute); continue; } } @@ -6909,7 +6882,7 @@ if (const ObjCInterfaceDecl *IDecl = dyn_cast(D)) { if (IDecl->getDefinition()) { D = IDecl->getDefinition(); - Result = D->getAvailability(Message); + Result = D->getAvailability(Message, &Attribute); } } @@ -6917,12 +6890,70 @@ if (Result == AR_Available) { const DeclContext *DC = ECD->getDeclContext(); if (const auto *TheEnumDecl = dyn_cast(DC)) { - Result = TheEnumDecl->getAvailability(Message); + Result = TheEnumDecl->getAvailability(Message, &Attribute); D = TheEnumDecl; } } - return {Result, D}; + // This decl could only have an inherited attribute, prefer pointing at the + // decl with the original availability attribute. + if (Attribute && Attribute->isInherited()) { + for (const NamedDecl *Redecl = D->getMostRecentDecl(); Redecl; + Redecl = cast_or_null(Redecl->getPreviousDecl())) { + for (const auto *RedeclAttr : Redecl->attrs()) { + if (RedeclAttr->isInherited()) + continue; + + if (isa(Attribute) && isa(RedeclAttr)) + return {AR_Unavailable, Redecl, cast(RedeclAttr)}; + + if (isa(Attribute) && isa(RedeclAttr)) + return {AR_Deprecated, Redecl, cast(RedeclAttr)}; + + const AvailabilityAttr *RedeclAA = + dyn_cast(RedeclAttr); + const AvailabilityAttr *OrigAA = dyn_cast(Attribute); + if (!RedeclAA || !OrigAA) + continue; + + // Check if this is an App Extension "platform", and if so chop off + // the suffix for matching with the actual platform. + StringRef ActualPlatform = RedeclAA->getPlatform()->getName(); + StringRef RealizedPlatform = ActualPlatform; + if (Redecl->getASTContext().getLangOpts().AppExt) { + size_t suffix = RealizedPlatform.rfind("_app_extension"); + if (suffix != StringRef::npos) + RealizedPlatform = RealizedPlatform.slice(0, suffix); + } + + StringRef TargetPlatform = + Redecl->getASTContext().getTargetInfo().getPlatformName(); + + // Match the platform name. + if (RealizedPlatform != TargetPlatform) + continue; + + bool EqualAttr; + switch (Result) { + case AR_NotYetIntroduced: + EqualAttr = RedeclAA->getIntroduced() == OrigAA->getIntroduced(); + break; + case AR_Deprecated: + EqualAttr = RedeclAA->getDeprecated() == OrigAA->getDeprecated(); + break; + case AR_Unavailable: + EqualAttr = RedeclAA->getObsoleted() == OrigAA->getObsoleted(); + break; + default: + EqualAttr = false; + } + if (EqualAttr) + return {Result, Redecl, RedeclAA}; + } + } + } + + return {Result, D, Attribute}; } @@ -6937,9 +6968,8 @@ // Checks if we should emit the availability diagnostic in the context of C. auto CheckContext = [&](const Decl *C) { if (K == AR_NotYetIntroduced) { - if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, C)) - if (AA->getIntroduced() >= DeclVersion) - return true; + if (C->getVersionIntroduced() >= DeclVersion) + return true; } else if (K == AR_Deprecated) if (C->isDeprecated()) return true; @@ -7045,6 +7075,7 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K, Decl *Ctx, const NamedDecl *ReferringDecl, const NamedDecl *OffendingDecl, + const Attr *OffendingAttr, StringRef Message, SourceLocation Loc, const ObjCInterfaceDecl *UnknownObjCClass, const ObjCPropertyDecl *ObjCProperty, @@ -7061,7 +7092,7 @@ unsigned available_here_select_kind; VersionTuple DeclVersion; - if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, OffendingDecl)) + if (const AvailabilityAttr *AA = dyn_cast(OffendingAttr)) DeclVersion = AA->getIntroduced(); if (!ShouldDiagnoseAvailabilityInContext(S, K, DeclVersion, Ctx)) @@ -7087,7 +7118,7 @@ property_note_select = /* unavailable */ 1; available_here_select_kind = /* unavailable */ 0; - if (auto attr = OffendingDecl->getAttr()) { + if (auto attr = dyn_cast(OffendingAttr)) { if (attr->isImplicit() && attr->getImplicitReason()) { // Most of these failures are due to extra restrictions in ARC; // reflect that in the primary diagnostic when applicable. @@ -7137,8 +7168,7 @@ // not specified for deployment targets >= to iOS 11 or equivalent or // for declarations that were introduced in iOS 11 (macOS 10.13, ...) or // later. - const AvailabilityAttr *AA = - getAttrForPlatform(S.getASTContext(), OffendingDecl); + const AvailabilityAttr *AA = cast(OffendingAttr); VersionTuple Introduced = AA->getIntroduced(); bool NewWarning = shouldDiagnoseAvailabilityByDefault( S.Context, S.Context.getTargetInfo().getPlatformMinVersion(), @@ -7161,9 +7191,9 @@ CharSourceRange UseRange; StringRef Replacement; if (K == AR_Deprecated) { - if (auto attr = OffendingDecl->getAttr()) + if (auto attr = dyn_cast(OffendingAttr)) Replacement = attr->getReplacement(); - if (auto attr = getAttrForPlatform(S.Context, OffendingDecl)) + if (auto attr = dyn_cast(OffendingAttr)) Replacement = attr->getReplacement(); if (!Replacement.empty()) @@ -7192,26 +7222,8 @@ S.Diag(UnknownObjCClass->getLocation(), diag::note_forward_class); } - // The declaration can have multiple availability attributes, we are looking - // at one of them. - const AvailabilityAttr *A = getAttrForPlatform(S.Context, OffendingDecl); - if (A && A->isInherited()) { - for (const Decl *Redecl = OffendingDecl->getMostRecentDecl(); Redecl; - Redecl = Redecl->getPreviousDecl()) { - const AvailabilityAttr *AForRedecl = getAttrForPlatform(S.Context, - Redecl); - if (AForRedecl && !AForRedecl->isInherited()) { - // If D is a declaration with inherited attributes, the note should - // point to the declaration with actual attributes. - S.Diag(Redecl->getLocation(), diag_available_here) << OffendingDecl - << available_here_select_kind; - break; - } - } - } - else - S.Diag(NoteLocation, diag_available_here) - << OffendingDecl << available_here_select_kind; + S.Diag(NoteLocation, diag_available_here) + << OffendingDecl << available_here_select_kind; if (K == AR_NotYetIntroduced) if (const auto *Enclosing = findEnclosingDeclToAnnotate(Ctx)) { @@ -7234,8 +7246,9 @@ DD.Triggered = true; DoEmitAvailabilityWarning( S, DD.getAvailabilityResult(), Ctx, DD.getAvailabilityReferringDecl(), - DD.getAvailabilityOffendingDecl(), DD.getAvailabilityMessage(), DD.Loc, - DD.getUnknownObjCClass(), DD.getObjCProperty(), false); + DD.getAvailabilityOffendingDecl(), DD.getAvailabilityAttr(), + DD.getAvailabilityMessage(), DD.Loc, DD.getUnknownObjCClass(), + DD.getObjCProperty(), false); } void Sema::PopParsingDeclaration(ParsingDeclState state, Decl *decl) { @@ -7296,23 +7309,23 @@ static void EmitAvailabilityWarning(Sema &S, AvailabilityResult AR, const NamedDecl *ReferringDecl, const NamedDecl *OffendingDecl, + const Attr *OffendingAttr, StringRef Message, SourceLocation Loc, const ObjCInterfaceDecl *UnknownObjCClass, const ObjCPropertyDecl *ObjCProperty, bool ObjCPropertyAccess) { // Delay if we're currently parsing a declaration. if (S.DelayedDiagnostics.shouldDelayDiagnostics()) { - S.DelayedDiagnostics.add( - DelayedDiagnostic::makeAvailability( - AR, Loc, ReferringDecl, OffendingDecl, UnknownObjCClass, - ObjCProperty, Message, ObjCPropertyAccess)); + S.DelayedDiagnostics.add(DelayedDiagnostic::makeAvailability( + AR, Loc, ReferringDecl, OffendingDecl, OffendingAttr, UnknownObjCClass, + ObjCProperty, Message, ObjCPropertyAccess)); return; } Decl *Ctx = cast(S.getCurLexicalContext()); DoEmitAvailabilityWarning(S, AR, Ctx, ReferringDecl, OffendingDecl, - Message, Loc, UnknownObjCClass, ObjCProperty, - ObjCPropertyAccess); + OffendingAttr, Message, Loc, UnknownObjCClass, + ObjCProperty, ObjCPropertyAccess); } namespace { @@ -7466,17 +7479,17 @@ NamedDecl *D, SourceRange Range) { AvailabilityResult Result; const NamedDecl *OffendingDecl; - std::tie(Result, OffendingDecl) = - ShouldDiagnoseAvailabilityOfDecl(D, nullptr); + const Attr *Attribute; + std::tie(Result, OffendingDecl, Attribute) = + ShouldDiagnoseAvailabilityOfDecl(D, nullptr); if (Result != AR_Available) { // All other diagnostic kinds have already been handled in // DiagnoseAvailabilityOfDecl. if (Result != AR_NotYetIntroduced) return; - const AvailabilityAttr *AA = - getAttrForPlatform(SemaRef.getASTContext(), OffendingDecl); - VersionTuple Introduced = AA->getIntroduced(); + VersionTuple Introduced = + cast(Attribute)->getIntroduced(); if (AvailabilityStack.back() >= Introduced) return; @@ -7653,8 +7666,10 @@ std::string Message; AvailabilityResult Result; const NamedDecl* OffendingDecl; + const Attr *Attribute; // See if this declaration is unavailable, deprecated, or partial. - std::tie(Result, OffendingDecl) = ShouldDiagnoseAvailabilityOfDecl(D, &Message); + std::tie(Result, OffendingDecl, Attribute) = + ShouldDiagnoseAvailabilityOfDecl(D, &Message); if (Result == AR_Available) return; @@ -7677,12 +7692,12 @@ const ObjCPropertyDecl *ObjCPDecl = nullptr; if (const ObjCMethodDecl *MD = dyn_cast(D)) { if (const ObjCPropertyDecl *PD = MD->findPropertyDecl()) { - AvailabilityResult PDeclResult = PD->getAvailability(nullptr); + AvailabilityResult PDeclResult = PD->getAvailability(); if (PDeclResult == Result) ObjCPDecl = PD; } } - EmitAvailabilityWarning(*this, Result, D, OffendingDecl, Message, Loc, - UnknownObjCClass, ObjCPDecl, ObjCPropertyAccess); + EmitAvailabilityWarning(*this, Result, D, OffendingDecl, Attribute, Message, + Loc, UnknownObjCClass, ObjCPDecl, ObjCPropertyAccess); } Index: test/Sema/attr-availability-ios.c =================================================================== --- test/Sema/attr-availability-ios.c +++ test/Sema/attr-availability-ios.c @@ -7,8 +7,8 @@ void f4(int) __attribute__((availability(macosx,introduced=10.1,deprecated=10.3,obsoleted=10.5), availability(ios,introduced=2.0,deprecated=2.1,obsoleted=3.0))); // expected-note{{explicitly marked unavailable}} void f5(int) __attribute__((availability(ios,introduced=2.0))) __attribute__((availability(ios,deprecated=3.0))); // expected-note {{'f5' has been explicitly marked deprecated here}} -void f6(int) __attribute__((availability(ios,deprecated=3.0))); -void f6(int) __attribute__((availability(ios,introduced=2.0))); // expected-note {{'f6' has been explicitly marked deprecated here}} +void f6(int) __attribute__((availability(ios,deprecated=3.0))); // expected-note {{'f6' has been explicitly marked deprecated here}} +void f6(int) __attribute__((availability(ios,introduced=2.0))); void test() { f0(0); // expected-warning{{'f0' is deprecated: first deprecated in iOS 2.1}} Index: test/Sema/attr-availability-tvos.c =================================================================== --- test/Sema/attr-availability-tvos.c +++ test/Sema/attr-availability-tvos.c @@ -7,8 +7,8 @@ void f4(int) __attribute__((availability(macosx,introduced=10.1,deprecated=10.3,obsoleted=10.5), availability(tvos,introduced=2.0,deprecated=2.1,obsoleted=3.0))); // expected-note{{explicitly marked unavailable}} void f5(int) __attribute__((availability(tvos,introduced=2.0))) __attribute__((availability(tvos,deprecated=3.0))); // expected-note {{'f5' has been explicitly marked deprecated here}} -void f6(int) __attribute__((availability(tvos,deprecated=3.0))); -void f6(int) __attribute__((availability(tvos,introduced=2.0))); // expected-note {{'f6' has been explicitly marked deprecated here}} +void f6(int) __attribute__((availability(tvos,deprecated=3.0))); // expected-note {{'f6' has been explicitly marked deprecated here}} +void f6(int) __attribute__((availability(tvos,introduced=2.0))); void test() { f0(0); // expected-warning{{'f0' is deprecated: first deprecated in tvOS 2.1}} @@ -43,8 +43,8 @@ void f5_attr_reversed_tvos(int) __attribute__((availability(ios, deprecated=3.0))) __attribute__((availability(tvos,introduced=2.0))); void f5b_tvos(int) __attribute__((availability(tvos,introduced=2.0))) __attribute__((availability(tvos,deprecated=3.0))); // expected-note {{'f5b_tvos' has been explicitly marked deprecated here}} void f5c_tvos(int) __attribute__((availability(ios,introduced=2.0))) __attribute__((availability(ios,deprecated=3.0))); // expected-note {{'f5c_tvos' has been explicitly marked deprecated here}} -void f6_tvos(int) __attribute__((availability(tvos,deprecated=3.0))); -void f6_tvos(int) __attribute__((availability(tvos,introduced=2.0))); // expected-note {{'f6_tvos' has been explicitly marked deprecated here}} +void f6_tvos(int) __attribute__((availability(tvos,deprecated=3.0))); // expected-note {{'f6_tvos' has been explicitly marked deprecated here}} +void f6_tvos(int) __attribute__((availability(tvos,introduced=2.0))); void test_tvos() { f0_tvos(0); // expected-warning{{'f0_tvos' is deprecated: first deprecated in tvOS 2.1}} Index: test/Sema/attr-availability-watchos.c =================================================================== --- test/Sema/attr-availability-watchos.c +++ test/Sema/attr-availability-watchos.c @@ -32,8 +32,8 @@ void f5_attr_reversed_watchos(int) __attribute__((availability(ios, deprecated=3.0))) __attribute__((availability(watchos,introduced=2.0))); void f5b_watchos(int) __attribute__((availability(watchos,introduced=2.0))) __attribute__((availability(watchos,deprecated=3.0))); // expected-note {{'f5b_watchos' has been explicitly marked deprecated here}} void f5c_watchos(int) __attribute__((availability(ios,introduced=2.0))) __attribute__((availability(ios,deprecated=3.0))); // expected-note {{'f5c_watchos' has been explicitly marked deprecated here}} -void f6_watchos(int) __attribute__((availability(watchos,deprecated=3.0))); -void f6_watchos(int) __attribute__((availability(watchos,introduced=2.0))); // expected-note {{'f6_watchos' has been explicitly marked deprecated here}} +void f6_watchos(int) __attribute__((availability(watchos,deprecated=3.0))); // expected-note {{'f6_watchos' has been explicitly marked deprecated here}} +void f6_watchos(int) __attribute__((availability(watchos,introduced=2.0))); void test_watchos() { f0_watchos(0); // expected-warning{{'f0_watchos' is deprecated: first deprecated in watchOS 2.1}} Index: test/Sema/attr-availability.c =================================================================== --- test/Sema/attr-availability.c +++ test/Sema/attr-availability.c @@ -16,7 +16,7 @@ ATSFontGetPostScriptName(int flags) __attribute__((availability(macosx,introduced=8.0,obsoleted=9.0, message="use ATSFontGetFullPostScriptName"))); // expected-note {{'ATSFontGetPostScriptName' has been explicitly marked unavailable here}} #if defined(WARN_PARTIAL) -// expected-note@+3 {{has been explicitly marked partial here}} +// expected-note@+3 2 {{has been explicitly marked partial here}} #endif extern void PartiallyAvailable() __attribute__((availability(macosx,introduced=10.8))); @@ -38,11 +38,6 @@ PartiallyAvailable(); } -#ifdef WARN_PARTIAL -// FIXME: This note should point to the declaration with the availability -// attribute. -// expected-note@+2 {{marked partial here}} -#endif extern void PartiallyAvailable() ; void with_redeclaration() { #ifdef WARN_PARTIAL Index: test/SemaObjC/protocol-attribute.m =================================================================== --- test/SemaObjC/protocol-attribute.m +++ test/SemaObjC/protocol-attribute.m @@ -1,7 +1,7 @@ // RUN: %clang_cc1 -fsyntax-only -verify %s __attribute ((unavailable)) -@protocol FwProto; // expected-note{{marked unavailable}} +@protocol FwProto; // expected-note 2 {{marked unavailable}} Class cFw = 0; // expected-error {{'FwProto' is unavailable}} @@ -33,7 +33,7 @@ Class clsP1 = 0; // expected-warning {{'MyProto1' is deprecated}} -@protocol FwProto @end // expected-note{{marked unavailable}} +@protocol FwProto @end @interface MyClass2 // expected-error {{'FwProto' is unavailable}} @end