diff --git a/include/swift/IDE/CompletionLookup.h b/include/swift/IDE/CompletionLookup.h index 9dca0b2bf7003..cdf47c718e7a8 100644 --- a/include/swift/IDE/CompletionLookup.h +++ b/include/swift/IDE/CompletionLookup.h @@ -612,7 +612,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { void getAttributeDeclCompletions(bool IsInSil, std::optional DK); - void getAttributeDeclParamCompletions(CustomSyntaxAttributeKind AttrKind, + void getAttributeDeclParamCompletions(ParameterizedDeclAttributeKind AttrKind, int ParamIndex, bool HasLabel); void getTypeAttributeKeywordCompletions(CompletionKind completionKind); diff --git a/include/swift/Parse/IDEInspectionCallbacks.h b/include/swift/Parse/IDEInspectionCallbacks.h index 809dde85833af..938083795af51 100644 --- a/include/swift/Parse/IDEInspectionCallbacks.h +++ b/include/swift/Parse/IDEInspectionCallbacks.h @@ -29,10 +29,13 @@ enum class ObjCSelectorContext { SetterSelector }; -/// Attributes that have syntax which can't be modelled using a function call. -/// This can't be \c DeclAttrKind because '@freestandig' and '@attached' have +/// Parameterized attributes that have code completion. +/// This can't be \c DeclAttrKind because '@freestanding' and '@attached' have /// the same attribute kind but take different macro roles as arguemnts. -enum class CustomSyntaxAttributeKind { +enum class ParameterizedDeclAttributeKind { + AccessControl, + Nonisolated, + Unowned, Available, FreestandingMacro, AttachedMacro, @@ -228,7 +231,7 @@ class CodeCompletionCallbacks { /// @available. /// If `HasLabel` is `true`, then the argument already has a label specified, /// e.g. we're completing after `names: ` in a macro declaration. - virtual void completeDeclAttrParam(CustomSyntaxAttributeKind DK, int Index, + virtual void completeDeclAttrParam(ParameterizedDeclAttributeKind DK, int Index, bool HasLabel){}; /// Complete 'async' and 'throws' at effects specifier position. diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index dc3b2ef54fbe1..bcca73daaca5a 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -114,7 +114,7 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks, SourceLoc DotLoc; TypeLoc ParsedTypeLoc; DeclContext *CurDeclContext = nullptr; - CustomSyntaxAttributeKind AttrKind; + ParameterizedDeclAttributeKind AttrKind; /// When the code completion token occurs in a custom attribute, the attribute /// it occurs in. Used so we can complete inside the attribute even if it's @@ -272,7 +272,7 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks, void completeCaseStmtKeyword() override; void completeCaseStmtBeginning(CodeCompletionExpr *E) override; void completeDeclAttrBeginning(bool Sil, bool isIndependent) override; - void completeDeclAttrParam(CustomSyntaxAttributeKind DK, int Index, + void completeDeclAttrParam(ParameterizedDeclAttributeKind DK, int Index, bool HasLabel) override; void completeEffectsSpecifier(bool hasAsync, bool hasThrows) override; void completeInPrecedenceGroup( @@ -460,7 +460,7 @@ void CodeCompletionCallbacksImpl::completeTypeSimpleBeginning() { } void CodeCompletionCallbacksImpl::completeDeclAttrParam( - CustomSyntaxAttributeKind DK, int Index, bool HasLabel) { + ParameterizedDeclAttributeKind DK, int Index, bool HasLabel) { Kind = CompletionKind::AttributeDeclParen; AttrKind = DK; AttrParamIndex = Index; diff --git a/lib/IDE/CompletionLookup.cpp b/lib/IDE/CompletionLookup.cpp index 91939902395c2..c259a6fe01596 100644 --- a/lib/IDE/CompletionLookup.cpp +++ b/lib/IDE/CompletionLookup.cpp @@ -3103,9 +3103,19 @@ void CompletionLookup::getAttributeDeclCompletions(bool IsInSil, } void CompletionLookup::getAttributeDeclParamCompletions( - CustomSyntaxAttributeKind AttrKind, int ParamIndex, bool HasLabel) { + ParameterizedDeclAttributeKind AttrKind, int ParamIndex, bool HasLabel) { switch (AttrKind) { - case CustomSyntaxAttributeKind::Available: + case ParameterizedDeclAttributeKind::Unowned: + addDeclAttrParamKeyword("safe", /*Parameters=*/{}, "", false); + addDeclAttrParamKeyword("unsafe", /*Parameters=*/{}, "", false); + break; + case ParameterizedDeclAttributeKind::Nonisolated: + addDeclAttrParamKeyword("unsafe", /*Parameters=*/{}, "", false); + break; + case ParameterizedDeclAttributeKind::AccessControl: + addDeclAttrParamKeyword("set", /*Parameters=*/{}, "", false); + break; + case ParameterizedDeclAttributeKind::Available: if (ParamIndex == 0) { addDeclAttrParamKeyword("*", /*Parameters=*/{}, "Platform", false); @@ -3126,15 +3136,15 @@ void CompletionLookup::getAttributeDeclParamCompletions( "Specify version number", true); } break; - case CustomSyntaxAttributeKind::FreestandingMacro: - case CustomSyntaxAttributeKind::AttachedMacro: + case ParameterizedDeclAttributeKind::FreestandingMacro: + case ParameterizedDeclAttributeKind::AttachedMacro: switch (ParamIndex) { case 0: for (auto role : getAllMacroRoles()) { bool isRoleSupported = isMacroSupported(role, Ctx); - if (AttrKind == CustomSyntaxAttributeKind::FreestandingMacro) { + if (AttrKind == ParameterizedDeclAttributeKind::FreestandingMacro) { isRoleSupported &= isFreestandingMacro(role); - } else if (AttrKind == CustomSyntaxAttributeKind::AttachedMacro) { + } else if (AttrKind == ParameterizedDeclAttributeKind::AttachedMacro) { isRoleSupported &= isAttachedMacro(role); } if (isRoleSupported) { @@ -3162,7 +3172,7 @@ void CompletionLookup::getAttributeDeclParamCompletions( break; } break; - case CustomSyntaxAttributeKind::StorageRestrictions: { + case ParameterizedDeclAttributeKind::StorageRestrictions: { bool suggestInitializesLabel = false; bool suggestAccessesLabel = false; bool suggestArgument = false; @@ -3295,7 +3305,7 @@ void CompletionLookup::getPrecedenceGroupCompletions( void CompletionLookup::getPoundAvailablePlatformCompletions() { // The platform names should be identical to those in @available. - getAttributeDeclParamCompletions(CustomSyntaxAttributeKind::Available, 0, + getAttributeDeclParamCompletions(ParameterizedDeclAttributeKind::Available, 0, /*HasLabel=*/false); } diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 534f70d9fd5dd..af4dce130d384 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -396,7 +396,7 @@ ParserResult Parser::parseExtendedAvailabilitySpecList( .highlight(SourceRange(ArgumentLoc)); if (Tok.is(tok::code_complete) && CodeCompletionCallbacks) { CodeCompletionCallbacks->completeDeclAttrParam( - CustomSyntaxAttributeKind::Available, ParamIndex, + ParameterizedDeclAttributeKind::Available, ParamIndex, /*HasLabel=*/false); consumeToken(tok::code_complete); } else { @@ -737,7 +737,7 @@ bool Parser::parseAvailability( !(Tok.isAnyOperator() && Tok.getText() == "*")) { if (Tok.is(tok::code_complete) && CodeCompletionCallbacks) { CodeCompletionCallbacks->completeDeclAttrParam( - CustomSyntaxAttributeKind::Available, 0, /*HasLabel=*/false); + ParameterizedDeclAttributeKind::Available, 0, /*HasLabel=*/false); consumeToken(tok::code_complete); } diagnose(Tok.getLoc(), diag::attr_availability_platform, AttrName) @@ -993,7 +993,7 @@ Parser::parseStorageRestrictionsAttribute(SourceLoc AtLoc, SourceLoc Loc) { } } this->CodeCompletionCallbacks->completeDeclAttrParam( - CustomSyntaxAttributeKind::StorageRestrictions, + ParameterizedDeclAttributeKind::StorageRestrictions, static_cast(completionKind), /*HasLabel=*/false); } } else if (parseIdentifier(propertyName, propertyNameLoc, @@ -1026,7 +1026,7 @@ Parser::parseStorageRestrictionsAttribute(SourceLoc AtLoc, SourceLoc Loc) { if (consumeIf(tok::code_complete)) { if (this->CodeCompletionCallbacks) { this->CodeCompletionCallbacks->completeDeclAttrParam( - CustomSyntaxAttributeKind::StorageRestrictions, + ParameterizedDeclAttributeKind::StorageRestrictions, static_cast(StorageRestrictionsCompletionKind::Label), /*HasLabel=*/false); } @@ -2214,11 +2214,11 @@ std::optional getMacroRole(StringRef roleName) { .Default(std::nullopt); } -static CustomSyntaxAttributeKind getCustomSyntaxAttributeKind(bool isAttached) { +static ParameterizedDeclAttributeKind getParameterizedDeclAttributeKind(bool isAttached) { if (isAttached) { - return CustomSyntaxAttributeKind::AttachedMacro; + return ParameterizedDeclAttributeKind::AttachedMacro; } else { - return CustomSyntaxAttributeKind::FreestandingMacro; + return ParameterizedDeclAttributeKind::FreestandingMacro; } } @@ -2265,13 +2265,13 @@ Parser::parseMacroRoleAttribute( sawRole = true; if (this->CodeCompletionCallbacks) { this->CodeCompletionCallbacks->completeDeclAttrParam( - getCustomSyntaxAttributeKind(isAttached), 0, + getParameterizedDeclAttributeKind(isAttached), 0, /*HasLabel=*/false); } } else if (!sawNames) { if (this->CodeCompletionCallbacks) { this->CodeCompletionCallbacks->completeDeclAttrParam( - getCustomSyntaxAttributeKind(isAttached), 1, + getParameterizedDeclAttributeKind(isAttached), 1, /*HasLabel=*/false); } } @@ -2373,7 +2373,7 @@ Parser::parseMacroRoleAttribute( status.setHasCodeCompletionAndIsError(); if (this->CodeCompletionCallbacks) { this->CodeCompletionCallbacks->completeDeclAttrParam( - getCustomSyntaxAttributeKind(isAttached), 1, /*HasLabel=*/true); + getParameterizedDeclAttributeKind(isAttached), 1, /*HasLabel=*/true); } } else if (parseIdentifier(introducedNameKind, introducedNameKindLoc, diag::macro_attribute_unknown_argument_form, @@ -2469,12 +2469,14 @@ Parser::parseMacroRoleAttribute( /// \c Identifier() ; if false, diagnose a missing argument list as an error. /// \param nonIdentifierDiagnostic The diagnostic to emit if something other than a /// \c tok::identifier is used as an argument. +/// \param PDK Specific kind of the parameterized attribute being passed. Used when invoking code completion callbacks. /// /// \returns \c None if an error was diagnosed; \c Identifier() if the argument list was permissibly /// omitted; the identifier written by the user otherwise. static std::optional parseSingleAttrOptionImpl( Parser &P, SourceLoc Loc, SourceRange &AttrRange, StringRef AttrName, - DeclAttrKind DK, bool allowOmitted, DiagRef nonIdentifierDiagnostic) { + DeclAttrKind DK, bool allowOmitted, DiagRef nonIdentifierDiagnostic, + std::optional PDK = std::nullopt) { SWIFT_DEFER { AttrRange = SourceRange(Loc, P.PreviousLoc); }; @@ -2483,12 +2485,17 @@ static std::optional parseSingleAttrOptionImpl( if (!P.Tok.is(tok::l_paren)) { if (allowOmitted) return Identifier(); - + P.diagnose(Loc, diag::attr_expected_lparen, AttrName, isDeclModifier); return std::nullopt; } P.consumeAttributeLParen(); + + if (P.Tok.is(tok::code_complete) && P.CodeCompletionCallbacks && PDK) { + P.CodeCompletionCallbacks->completeDeclAttrParam(*PDK, 0, /*HasLabel=*/false); + P.consumeToken(tok::code_complete); + } StringRef parsedName = P.Tok.getText(); if (!P.consumeIf(tok::identifier)) { @@ -2590,9 +2597,9 @@ ParserResult Parser::parseLifetimeAttribute(SourceLoc atLoc, /// \returns \c None if an error was diagnosed; \c Identifier() if the argument list was permissibly /// omitted; the identifier written by the user otherwise. static std::optional -parseSingleAttrOptionIdentifier(Parser &P, SourceLoc Loc, - SourceRange &AttrRange, StringRef AttrName, - DeclAttrKind DK, bool allowOmitted = false) { +parseSingleAttrOptionIdentifier(Parser &P, SourceLoc Loc, SourceRange &AttrRange, + StringRef AttrName, DeclAttrKind DK, + bool allowOmitted = false) { return parseSingleAttrOptionImpl( P, Loc, AttrRange, AttrName, DK, allowOmitted, {diag::attr_expected_option_identifier, {AttrName}}); @@ -2609,6 +2616,7 @@ parseSingleAttrOptionIdentifier(Parser &P, SourceLoc Loc, /// \param options The set of permitted keywords and their corresponding values. /// \param valueIfOmitted If present, treat a missing argument list as permitted and return /// the provided value; if absent, diagnose a missing argument list as an error. +/// \param PDK Specific kind of the parameterized attribute being passed. Used when invoking code completion callbacks. /// /// \returns \c None if an error was diagnosed; the value corresponding to the identifier written by the /// user otherwise. @@ -2617,12 +2625,14 @@ static std::optional parseSingleAttrOption(Parser &P, SourceLoc Loc, SourceRange &AttrRange, StringRef AttrName, DeclAttrKind DK, ArrayRef> options, - std::optional valueIfOmitted = std::nullopt) { + std::optional valueIfOmitted = std::nullopt, + std::optional PDK = std::nullopt) { auto parsedIdentifier = parseSingleAttrOptionImpl( - P, Loc, AttrRange,AttrName, DK, + P, Loc, AttrRange, AttrName, DK, /*allowOmitted=*/valueIfOmitted.has_value(), {diag::attr_expected_option_such_as, - {AttrName, options.front().first.str()}}); + {AttrName, options.front().first.str()}}, PDK); + if (!parsedIdentifier) return std::nullopt; @@ -2811,7 +2821,8 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, *this, Loc, AttrRange, AttrName, DK, { { Context.Id_unsafe, ReferenceOwnership::Unmanaged }, { Context.Id_safe, ReferenceOwnership::Unowned } - }, ReferenceOwnership::Unowned) + }, ReferenceOwnership::Unowned, + ParameterizedDeclAttributeKind::Unowned) // Recover from errors by going back to Unowned. .value_or(ReferenceOwnership::Unowned); } @@ -2882,11 +2893,21 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, consumeAttributeLParen(); + if (Tok.is(tok::code_complete)) { + if (CodeCompletionCallbacks) { + CodeCompletionCallbacks->completeDeclAttrParam( + ParameterizedDeclAttributeKind::AccessControl, 0, /*HasLabel=*/false); + consumeToken(tok::code_complete); + } + return makeParserSuccess(); + } + // Parse the subject. if (Tok.isContextualKeyword("set")) { consumeToken(); } else { diagnose(Loc, diag::attr_access_expected_set, AttrName); + // Minimal recovery: if there's a single token and then an r_paren, // consume them both. If there's just an r_paren, consume that. if (!consumeIf(tok::r_paren)) { @@ -3155,8 +3176,8 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, case DeclAttrKind::SwiftNativeObjCRuntimeBase: { SourceRange range; - auto name = parseSingleAttrOptionIdentifier(*this, Loc, range, AttrName, - DK); + auto name = parseSingleAttrOptionIdentifier(*this, Loc, range, + AttrName, DK); if (!name) return makeParserSuccess(); @@ -3721,7 +3742,8 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, if (EnableParameterizedNonisolated) { isUnsafe = parseSingleAttrOption(*this, Loc, AttrRange, AttrName, DK, - {{Context.Id_unsafe, true}}, *isUnsafe); + {{Context.Id_unsafe, true}}, *isUnsafe, + ParameterizedDeclAttributeKind::Nonisolated); if (!isUnsafe) { return makeParserSuccess(); } @@ -5616,33 +5638,47 @@ bool swift::isKeywordPossibleDeclStart(const LangOptions &options, } static bool -isParenthesizedModifier(Parser &P, StringRef name, - std::initializer_list allowedArguments) { +consumeIfParenthesizedModifier(Parser &P, StringRef name, + std::initializer_list allowedArguments) { assert((P.Tok.getText() == name) && P.peekToken().is(tok::l_paren) && "Invariant violated"); // Look ahead to parse the parenthesized expression. - Parser::BacktrackingScope Backtrack(P); + Parser::CancellableBacktrackingScope backtrack(P); P.consumeToken(tok::identifier); P.consumeToken(tok::l_paren); - + + if (P.consumeIf(tok::code_complete)) { + // If a code complete token is present, recover from missing/incorrect argument and missing '(' + P.consumeIf(tok::identifier); + P.consumeIf(tok::r_paren); + backtrack.cancelBacktrack(); + return true; + } + const bool argumentIsAllowed = std::find(allowedArguments.begin(), allowedArguments.end(), P.Tok.getText()) != allowedArguments.end(); - return argumentIsAllowed && P.Tok.is(tok::identifier) && - P.peekToken().is(tok::r_paren); + + if (argumentIsAllowed && P.consumeIf(tok::identifier) && + P.consumeIf(tok::r_paren)) { + backtrack.cancelBacktrack(); + return true; + } + + return false; } /// Given a current token of 'unowned', check to see if it is followed by a -/// "(safe)" or "(unsafe)" specifier. -static bool isParenthesizedUnowned(Parser &P) { - return isParenthesizedModifier(P, "unowned", {"safe", "unsafe"}); +/// "(safe)" or "(unsafe)" specifier and consumes if it is. +static bool consumeIfParenthesizedUnowned(Parser &P) { + return consumeIfParenthesizedModifier(P, "unowned", {"safe", "unsafe"}); } /// Given a current token of 'nonisolated', check to see if it is followed by an -/// "(unsafe)" specifier. -static bool isParenthesizedNonisolated(Parser &P) { - return isParenthesizedModifier(P, "nonisolated", {"unsafe"}); +/// "(unsafe)" specifier and consumes if it is. +static bool consumeIfParenthesizedNonisolated(Parser &P) { + return consumeIfParenthesizedModifier(P, "nonisolated", {"unsafe"}); } static void skipAttribute(Parser &P) { @@ -5803,27 +5839,21 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes, // If it might be, we do some more digging. // If this is 'unowned', check to see if it is valid. - if (Tok.getText() == "unowned" && Tok2.is(tok::l_paren) && - isParenthesizedUnowned(*this)) { + if (Tok.getText() == "unowned" && Tok2.is(tok::l_paren)) { Parser::BacktrackingScope Backtrack(*this); - consumeToken(tok::identifier); - consumeToken(tok::l_paren); - consumeToken(tok::identifier); - consumeToken(tok::r_paren); - return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, - /*hadAttrsOrModifiers=*/true); + if (consumeIfParenthesizedUnowned(*this)) { + return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, + /*hadAttrsOrModifiers=*/true); + } } // If this is 'nonisolated', check to see if it is valid. - if (Tok.isContextualKeyword("nonisolated") && Tok2.is(tok::l_paren) && - isParenthesizedNonisolated(*this)) { + if (Tok.isContextualKeyword("nonisolated") && Tok2.is(tok::l_paren)) { BacktrackingScope backtrack(*this); - consumeToken(tok::identifier); - consumeToken(tok::l_paren); - consumeToken(tok::identifier); - consumeToken(tok::r_paren); - return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, - /*hadAttrsOrModifiers=*/true); + if (consumeIfParenthesizedNonisolated(*this)) { + return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, + /*hadAttrsOrModifiers=*/true); + } } if (Tok.isContextualKeyword("actor")) { diff --git a/test/IDE/complete_access_control_set.swift b/test/IDE/complete_access_control_set.swift new file mode 100644 index 0000000000000..d006fd10b6e05 --- /dev/null +++ b/test/IDE/complete_access_control_set.swift @@ -0,0 +1,27 @@ +// RUN: %batch-code-completion + +// ACCESS_CONTROL_SET: Keyword/None: set; name=set + +public(#^PUBLIC_TOP_LEVEL?check=ACCESS_CONTROL_SET^#) var var1 = 0 + +private(#^PRIVATE_TOP_LEVEL?check=ACCESS_CONTROL_SET^#) var var2 = 0 + +internal(#^INTERNAL_TOP_LEVEL?check=ACCESS_CONTROL_SET^#) var var3 = 0 + +fileprivate(#^FILEPRIVATE_TOP_LEVEL?check=ACCESS_CONTROL_SET^#) var var4 = 0 + +package(#^PACKAGE_TOP_LEVEL?check=ACCESS_CONTROL_SET^#) var var5 = 0 + +struct MyStruct { + public(#^PUBLIC_IN_STRUCT?check=ACCESS_CONTROL_SET^#) var prop1: Int = 0 + + private(#^PRIVATE_IN_STRUCT?check=ACCESS_CONTROL_SET^#) var prop2: Int = 0 + + open(#^OPEN_IN_STRUCT?check=ACCESS_CONTROL_SET^#) var prop3: Int = 0 + + internal(#^INTERNAL_IN_STRUCT?check=ACCESS_CONTROL_SET^#) var prop4: Int = 0 + + fileprivate(#^FILEPRIVATE_IN_STRUCT?check=ACCESS_CONTROL_SET^#) var prop5: Int = 0 + + package(#^PACKAGE_IN_STRUCT?check=ACCESS_CONTROL_SET^#) var prop6: Int = 0 +} diff --git a/test/IDE/complete_nonisolated_unsafe.swift b/test/IDE/complete_nonisolated_unsafe.swift new file mode 100644 index 0000000000000..54edd05f1ac14 --- /dev/null +++ b/test/IDE/complete_nonisolated_unsafe.swift @@ -0,0 +1,9 @@ +// RUN: %batch-code-completion + +// NONISOLATED_UNSAFE: Keyword/None: unsafe; name=unsafe + +nonisolated(#^NONISOLATED_UNSAFE_TOP_LEVEL?check=NONISOLATED_UNSAFE^#) var count = 0 + +struct MyStruct { + nonisolated(#^NONISOLATED_UNSAFE_IN_STRUCT?check=NONISOLATED_UNSAFE^#) var prop = 0 +} diff --git a/test/IDE/complete_unowned_parameter.swift b/test/IDE/complete_unowned_parameter.swift new file mode 100644 index 0000000000000..d3a73a720cf0c --- /dev/null +++ b/test/IDE/complete_unowned_parameter.swift @@ -0,0 +1,10 @@ +// RUN: %batch-code-completion + +// UNOWNED_PARAMETER-DAG: Keyword/None: safe; name=safe +// UNOWNED_PARAMETER-DAG: Keyword/None: unsafe; name=unsafe + +unowned(#^UNOWNED_TOP_LEVEL?check=UNOWNED_PARAMETER^#) var count = 0 + +struct MyStruct { + unowned(#^UNOWNED_IN_STRUCT?check=UNOWNED_PARAMETER^#) var prop: Int = 0 +}