mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[CodeCompletion] Add completion for @storageRestrictions
This commit is contained in:
@@ -105,6 +105,9 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
|
||||
TypeInDeclContext,
|
||||
ImportFromModule,
|
||||
GenericRequirement,
|
||||
|
||||
/// Look up stored properties within a type.
|
||||
StoredProperty,
|
||||
};
|
||||
|
||||
LookupKind Kind;
|
||||
@@ -528,6 +531,9 @@ public:
|
||||
void getValueExprCompletions(Type ExprType, ValueDecl *VD = nullptr,
|
||||
bool IsDeclUnapplied = false);
|
||||
|
||||
/// Add completions for stored properties of \p D.
|
||||
void getStoredPropertyCompletions(const NominalTypeDecl *D);
|
||||
|
||||
void collectOperators(SmallVectorImpl<OperatorDecl *> &results);
|
||||
|
||||
void addPostfixBang(Type resultType);
|
||||
|
||||
@@ -36,6 +36,25 @@ enum class CustomSyntaxAttributeKind {
|
||||
Available,
|
||||
FreestandingMacro,
|
||||
AttachedMacro,
|
||||
StorageRestrictions
|
||||
};
|
||||
|
||||
/// A bit of a hack. When completing inside the '@storageRestrictions'
|
||||
/// attribute, we use the \c ParamIndex field to communicate where inside the
|
||||
/// attribute we are performing the completion.
|
||||
enum class StorageRestrictionsCompletionKind : int {
|
||||
/// We are completing directly after the '(' and require a 'initializes' or
|
||||
/// 'accesses' label.
|
||||
Label,
|
||||
/// We are completing in a context that only allows arguments (ie. accessed or
|
||||
/// initialized variables) and doesn't permit an argument label.
|
||||
Argument,
|
||||
/// Completion in a context that allows either an argument or the
|
||||
/// 'initializes' label.
|
||||
ArgumentOrInitializesLabel,
|
||||
/// Completion in a context that allows either an argument or the
|
||||
/// 'accesses' label.
|
||||
ArgumentOrAccessesLabel
|
||||
};
|
||||
|
||||
/// Parser's interface to code completion.
|
||||
|
||||
@@ -1305,6 +1305,7 @@ bool CompletionLookup::isImplicitlyCurriedInstanceMethod(
|
||||
|
||||
switch (Kind) {
|
||||
case LookupKind::ValueExpr:
|
||||
case LookupKind::StoredProperty:
|
||||
return ExprType->is<AnyMetatypeType>();
|
||||
case LookupKind::ValueInDeclContext:
|
||||
if (InsideStaticMethod)
|
||||
@@ -2248,6 +2249,13 @@ void CompletionLookup::foundDecl(ValueDecl *D, DeclVisibilityKind Reason,
|
||||
}
|
||||
|
||||
return;
|
||||
case LookupKind::StoredProperty:
|
||||
if (auto *VD = dyn_cast<VarDecl>(D)) {
|
||||
if (VD->getImplInfo().hasStorage()) {
|
||||
addVarDeclRef(VD, Reason, dynamicLookupInfo);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2463,6 +2471,17 @@ void CompletionLookup::getValueExprCompletions(Type ExprType, ValueDecl *VD,
|
||||
/*includeProtocolExtensionMembers*/ true);
|
||||
}
|
||||
|
||||
void CompletionLookup::getStoredPropertyCompletions(const NominalTypeDecl *D) {
|
||||
Kind = LookupKind::StoredProperty;
|
||||
NeedLeadingDot = false;
|
||||
|
||||
lookupVisibleMemberDecls(*this, D->getDeclaredInterfaceType(),
|
||||
/*DotLoc=*/SourceLoc(), CurrDeclContext,
|
||||
/*IncludeInstanceMembers*/ true,
|
||||
/*includeDerivedRequirements*/ false,
|
||||
/*includeProtocolExtensionMembers*/ false);
|
||||
}
|
||||
|
||||
void CompletionLookup::collectOperators(
|
||||
SmallVectorImpl<OperatorDecl *> &results) {
|
||||
assert(CurrDeclContext);
|
||||
@@ -3051,6 +3070,43 @@ void CompletionLookup::getAttributeDeclParamCompletions(
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CustomSyntaxAttributeKind::StorageRestrictions: {
|
||||
bool suggestInitializesLabel = false;
|
||||
bool suggestAccessesLabel = false;
|
||||
bool suggestArgument = false;
|
||||
switch (static_cast<StorageRestrictionsCompletionKind>(ParamIndex)) {
|
||||
case StorageRestrictionsCompletionKind::Label:
|
||||
suggestAccessesLabel = true;
|
||||
suggestInitializesLabel = true;
|
||||
break;
|
||||
case StorageRestrictionsCompletionKind::Argument:
|
||||
suggestArgument = true;
|
||||
break;
|
||||
case StorageRestrictionsCompletionKind::ArgumentOrInitializesLabel:
|
||||
suggestArgument = true;
|
||||
suggestInitializesLabel = true;
|
||||
break;
|
||||
case StorageRestrictionsCompletionKind::ArgumentOrAccessesLabel:
|
||||
suggestArgument = true;
|
||||
suggestAccessesLabel = true;
|
||||
break;
|
||||
}
|
||||
if (suggestInitializesLabel) {
|
||||
addDeclAttrParamKeyword(
|
||||
"initializes", /*Parameters=*/{},
|
||||
"Specify stored properties initialized by the accessor", true);
|
||||
}
|
||||
if (suggestAccessesLabel) {
|
||||
addDeclAttrParamKeyword(
|
||||
"accesses", /*Parameters=*/{},
|
||||
"Specify stored properties accessed by the accessor", true);
|
||||
}
|
||||
if (suggestArgument) {
|
||||
if (auto NT = dyn_cast<NominalTypeDecl>(CurrDeclContext)) {
|
||||
getStoredPropertyCompletions(NT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1184,11 +1184,16 @@ Parser::parseStorageRestrictionsAttribute(SourceLoc AtLoc, SourceLoc Loc) {
|
||||
SmallVector<Identifier> initializesProperties;
|
||||
SmallVector<Identifier> accessesProperties;
|
||||
|
||||
auto parseProperties = [&](SourceLoc loc, Identifier label,
|
||||
SmallVectorImpl<Identifier> &properties) {
|
||||
enum class AccessKind { Initialization, Access, Invalid };
|
||||
|
||||
auto parseProperties =
|
||||
[&](SourceLoc loc, Identifier label, AccessKind kind,
|
||||
SmallVectorImpl<Identifier> &properties) -> ParserStatus {
|
||||
ParserStatus status;
|
||||
if (!properties.empty()) {
|
||||
diagnose(loc, diag::duplicate_storage_restrictions_attr_label, label);
|
||||
return true;
|
||||
status.setIsParseError();
|
||||
return status;
|
||||
}
|
||||
|
||||
bool hasNextProperty = false;
|
||||
@@ -1199,10 +1204,49 @@ Parser::parseStorageRestrictionsAttribute(SourceLoc AtLoc, SourceLoc Loc) {
|
||||
|
||||
Identifier propertyName;
|
||||
SourceLoc propertyNameLoc;
|
||||
if (parseIdentifier(propertyName, propertyNameLoc,
|
||||
diag::storage_restrictions_attr_expected_name,
|
||||
/*diagnoseDollarPrefix=*/true)) {
|
||||
return true;
|
||||
if (consumeIf(tok::code_complete)) {
|
||||
status.setHasCodeCompletion();
|
||||
if (this->CodeCompletionCallbacks) {
|
||||
StorageRestrictionsCompletionKind completionKind;
|
||||
if (properties.empty()) {
|
||||
// We are parsing the first argument after a label. The argument is
|
||||
// required.
|
||||
completionKind = StorageRestrictionsCompletionKind::Argument;
|
||||
} else if (!initializesProperties.empty() &&
|
||||
!accessesProperties.empty()) {
|
||||
// We have specified both an 'initializes' and an 'accesses' label
|
||||
// already. All we can accept are arguments now.
|
||||
completionKind = StorageRestrictionsCompletionKind::Argument;
|
||||
} else {
|
||||
// We can accept either an argument or the label that hasn't been
|
||||
// specified yet.
|
||||
switch (kind) {
|
||||
case AccessKind::Access:
|
||||
completionKind =
|
||||
StorageRestrictionsCompletionKind::ArgumentOrInitializesLabel;
|
||||
break;
|
||||
case AccessKind::Initialization:
|
||||
completionKind =
|
||||
StorageRestrictionsCompletionKind::ArgumentOrAccessesLabel;
|
||||
break;
|
||||
case AccessKind::Invalid:
|
||||
// We should never end up in this case. 'parseProperties' does not
|
||||
// get called with 'AccessKind::Invalid'.
|
||||
// If we do end up here, suggest arguments instead of crashing
|
||||
// since it's the most sensible fallback.
|
||||
completionKind = StorageRestrictionsCompletionKind::Argument;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->CodeCompletionCallbacks->completeDeclAttrParam(
|
||||
CustomSyntaxAttributeKind::StorageRestrictions,
|
||||
static_cast<int>(completionKind), /*HasLabel=*/false);
|
||||
}
|
||||
} else if (parseIdentifier(propertyName, propertyNameLoc,
|
||||
diag::storage_restrictions_attr_expected_name,
|
||||
/*diagnoseDollarPrefix=*/true)) {
|
||||
status.setIsParseError();
|
||||
return status;
|
||||
}
|
||||
|
||||
properties.push_back(propertyName);
|
||||
@@ -1211,24 +1255,36 @@ Parser::parseStorageRestrictionsAttribute(SourceLoc AtLoc, SourceLoc Loc) {
|
||||
hasNextProperty = consumeIf(tok::comma);
|
||||
} while (hasNextProperty);
|
||||
|
||||
return false;
|
||||
return status;
|
||||
};
|
||||
|
||||
auto parseArgument = [&](bool isOptional = false) -> bool {
|
||||
auto parseArgument = [&](bool isOptional = false) -> ParserStatus {
|
||||
ParserStatus status;
|
||||
|
||||
if (Tok.is(tok::r_paren) && isOptional)
|
||||
return false;
|
||||
return status;
|
||||
|
||||
Identifier accessLabel;
|
||||
SourceLoc loc;
|
||||
parseOptionalArgumentLabel(accessLabel, loc);
|
||||
|
||||
if (accessLabel.empty()) {
|
||||
diagnose(Loc, diag::missing_storage_restrictions_attr_label);
|
||||
return true;
|
||||
if (consumeIf(tok::code_complete)) {
|
||||
if (this->CodeCompletionCallbacks) {
|
||||
this->CodeCompletionCallbacks->completeDeclAttrParam(
|
||||
CustomSyntaxAttributeKind::StorageRestrictions,
|
||||
static_cast<int>(StorageRestrictionsCompletionKind::Label),
|
||||
/*HasLabel=*/false);
|
||||
}
|
||||
status.setHasCodeCompletion();
|
||||
return status;
|
||||
} else {
|
||||
diagnose(Loc, diag::missing_storage_restrictions_attr_label);
|
||||
status.setIsParseError();
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
enum class AccessKind { Initialization, Access, Invalid };
|
||||
|
||||
auto access = llvm::StringSwitch<AccessKind>(accessLabel.str())
|
||||
.Case("initializes", AccessKind::Initialization)
|
||||
.Case("accesses", AccessKind::Access)
|
||||
@@ -1236,20 +1292,25 @@ Parser::parseStorageRestrictionsAttribute(SourceLoc AtLoc, SourceLoc Loc) {
|
||||
|
||||
switch (access) {
|
||||
case AccessKind::Initialization:
|
||||
return parseProperties(loc, accessLabel, initializesProperties);
|
||||
return parseProperties(loc, accessLabel, access, initializesProperties);
|
||||
|
||||
case AccessKind::Access:
|
||||
return parseProperties(loc, accessLabel, accessesProperties);
|
||||
return parseProperties(loc, accessLabel, access, accessesProperties);
|
||||
|
||||
case AccessKind::Invalid:
|
||||
diagnose(loc, diag::invalid_storage_restrictions_attr_label, accessLabel);
|
||||
return true;
|
||||
status.setIsParseError();
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
ParserStatus argumentStatus = parseArgument();
|
||||
// Attribute should have at least one argument.
|
||||
if (parseArgument() || parseArgument(/*isOptional=*/true)) {
|
||||
Status.setIsParseError();
|
||||
if (!argumentStatus.isErrorOrHasCompletion()) {
|
||||
argumentStatus |= parseArgument(/*isOptional=*/true);
|
||||
}
|
||||
Status |= argumentStatus;
|
||||
if (argumentStatus.isErrorOrHasCompletion()) {
|
||||
// Let's skip ahead to `)` to recover.
|
||||
skipUntil(tok::r_paren);
|
||||
}
|
||||
@@ -1262,13 +1323,10 @@ Parser::parseStorageRestrictionsAttribute(SourceLoc AtLoc, SourceLoc Loc) {
|
||||
Status.setIsParseError();
|
||||
}
|
||||
|
||||
if (Status.isErrorOrHasCompletion()) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
return ParserResult<StorageRestrictionsAttr>(StorageRestrictionsAttr::create(
|
||||
auto attribute = StorageRestrictionsAttr::create(
|
||||
Context, AtLoc, SourceRange(Loc, rParenLoc), initializesProperties,
|
||||
accessesProperties));
|
||||
accessesProperties);
|
||||
return makeParserResult(Status, attribute);
|
||||
}
|
||||
|
||||
ParserResult<ImplementsAttr>
|
||||
@@ -3525,8 +3583,10 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
|
||||
case DAK_StorageRestrictions: {
|
||||
ParserResult<StorageRestrictionsAttr> Attr =
|
||||
parseStorageRestrictionsAttribute(AtLoc, Loc);
|
||||
if (Attr.isNonNull()) {
|
||||
if (!Attr.isParseErrorOrHasCompletion()) {
|
||||
Attributes.add(Attr.get());
|
||||
} else {
|
||||
return Attr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -7361,12 +7421,22 @@ struct Parser::ParsedAccessors {
|
||||
}
|
||||
};
|
||||
|
||||
static bool parseAccessorIntroducer(Parser &P,
|
||||
DeclAttributes &Attributes,
|
||||
AccessorKind &Kind,
|
||||
SourceLoc &Loc) {
|
||||
static ParserStatus parseAccessorIntroducer(Parser &P,
|
||||
DeclAttributes &Attributes,
|
||||
AccessorKind &Kind,
|
||||
SourceLoc &Loc) {
|
||||
ParserStatus Status;
|
||||
assert(Attributes.isEmpty());
|
||||
P.parseDeclAttributeList(Attributes);
|
||||
auto AttributeStatus = P.parseDeclAttributeList(Attributes);
|
||||
// Only propagate the `hasCodeCompletion` status outwards. If the attributes
|
||||
// have an error, we want to just drop them and continue parsing as if they
|
||||
// weren't present.
|
||||
if (AttributeStatus.hasCodeCompletion()) {
|
||||
Status.setHasCodeCompletion();
|
||||
}
|
||||
if (Status.hasCodeCompletion() && P.CodeCompletionCallbacks) {
|
||||
P.CodeCompletionCallbacks->setAttrTargetDeclKind(DeclKind::Accessor);
|
||||
}
|
||||
|
||||
// Parse the contextual keywords for 'mutating' and 'nonmutating' before
|
||||
// get and set.
|
||||
@@ -7386,7 +7456,8 @@ static bool parseAccessorIntroducer(Parser &P,
|
||||
|
||||
if (!(P.Tok.is(tok::identifier) || P.Tok.is(tok::kw_init)) ||
|
||||
P.Tok.isEscapedIdentifier()) {
|
||||
return true;
|
||||
Status.setIsParseError();
|
||||
return Status;
|
||||
}
|
||||
#define SUPPRESS_ARTIFICIAL_ACCESSORS 1
|
||||
#define ACCESSOR_KEYWORD(KEYWORD)
|
||||
@@ -7396,11 +7467,12 @@ static bool parseAccessorIntroducer(Parser &P,
|
||||
}
|
||||
#include "swift/AST/AccessorKinds.def"
|
||||
else {
|
||||
return true;
|
||||
Status.setIsParseError();
|
||||
return Status;
|
||||
}
|
||||
P.Tok.setKind(tok::contextual_keyword);
|
||||
Loc = P.consumeToken();
|
||||
return false;
|
||||
return Status;
|
||||
}
|
||||
|
||||
/// Parsing for effects specifiers. We expect the token cursor to be
|
||||
@@ -7600,9 +7672,10 @@ ParserStatus Parser::parseGetSet(ParseDeclOptions Flags, ParameterList *Indices,
|
||||
DeclAttributes Attributes;
|
||||
AccessorKind Kind = AccessorKind::Get;
|
||||
SourceLoc Loc;
|
||||
bool NotAccessor = parseAccessorIntroducer(
|
||||
*this, Attributes, Kind, Loc);
|
||||
if (NotAccessor) {
|
||||
ParserStatus AccessorStatus =
|
||||
parseAccessorIntroducer(*this, Attributes, Kind, Loc);
|
||||
Status |= AccessorStatus;
|
||||
if (AccessorStatus.isError() && !AccessorStatus.hasCodeCompletion()) {
|
||||
if (Tok.is(tok::code_complete)) {
|
||||
// Handle code completion here only if it's not the first accessor.
|
||||
// If it's the first accessor, it's handled in function body parsing
|
||||
@@ -7715,8 +7788,9 @@ void Parser::parseTopLevelAccessors(
|
||||
DeclAttributes attributes;
|
||||
AccessorKind kind = AccessorKind::Get;
|
||||
SourceLoc loc;
|
||||
bool notAccessor = parseAccessorIntroducer(*this, attributes, kind, loc);
|
||||
if (notAccessor)
|
||||
ParserStatus accessorStatus =
|
||||
parseAccessorIntroducer(*this, attributes, kind, loc);
|
||||
if (accessorStatus.isError())
|
||||
break;
|
||||
|
||||
(void)parseAccessorAfterIntroducer(
|
||||
@@ -7866,9 +7940,7 @@ Parser::parseDeclVarGetSet(PatternBindingEntry &entry, ParseDeclOptions Flags,
|
||||
auto *resultTypeRepr = typedPattern ? typedPattern->getTypeRepr() : nullptr;
|
||||
auto AccessorStatus = parseGetSet(Flags, /*Indices=*/nullptr, resultTypeRepr,
|
||||
accessors, storage, StaticLoc);
|
||||
if (AccessorStatus.hasCodeCompletion())
|
||||
return makeParserCodeCompletionStatus();
|
||||
if (AccessorStatus.isErrorOrHasCompletion())
|
||||
if (AccessorStatus.isError())
|
||||
Invalid = true;
|
||||
|
||||
// If we have an invalid case, bail out now.
|
||||
@@ -7907,7 +7979,7 @@ Parser::parseDeclVarGetSet(PatternBindingEntry &entry, ParseDeclOptions Flags,
|
||||
setOriginalDeclarationForDifferentiableAttributes(accessor->getAttrs(),
|
||||
accessor);
|
||||
|
||||
return makeParserResult(PrimaryVar);
|
||||
return makeParserResult(AccessorStatus, PrimaryVar);
|
||||
}
|
||||
|
||||
/// Add the given accessor to the collection of parsed accessors. If
|
||||
|
||||
67
test/IDE/complete_storage_restriction.swift
Normal file
67
test/IDE/complete_storage_restriction.swift
Normal file
@@ -0,0 +1,67 @@
|
||||
// RUN: %batch-code-completion
|
||||
|
||||
struct Test {
|
||||
var storedProperty: Int
|
||||
|
||||
var attrBegin: Int {
|
||||
@#^ATTRIBUTE_BEGIN^# init(newValue) {
|
||||
print(newValue)
|
||||
}
|
||||
get { 1 }
|
||||
}
|
||||
// ATTRIBUTE_BEGIN: Keyword/None: storageRestrictions[#Accessor Attribute#];
|
||||
|
||||
var bothLabels: Int {
|
||||
@storageRestrictions(#^AFTER_PAREN^#)
|
||||
init(newValue) {
|
||||
}
|
||||
get { 1 }
|
||||
}
|
||||
// AFTER_PAREN: Begin completions, 2 items
|
||||
// AFTER_PAREN-DAG: Keyword/None: initializes: [#Specify stored properties initialized by the accessor#];
|
||||
// AFTER_PAREN-DAG: Keyword/None: accesses: [#Specify stored properties accessed by the accessor#];
|
||||
|
||||
var secondAccessesArgument: Int {
|
||||
@storageRestrictions(accesses: x, #^SECOND_ACCESSES_ARGUMENT^#)
|
||||
init(newValue) {
|
||||
}
|
||||
}
|
||||
// SECOND_ACCESSES_ARGUMENT: Begin completions, 2 items
|
||||
// SECOND_ACCESSES_ARGUMENT-DAG: Keyword/None: initializes: [#Specify stored properties initialized by the accessor#];
|
||||
// SECOND_ACCESSES_ARGUMENT-DAG: Decl[InstanceVar]/CurrNominal: storedProperty[#Int#];
|
||||
|
||||
|
||||
var secondInitializesArgument: Int {
|
||||
@storageRestrictions(initializes: x, #^SECOND_INITIALIZES_ARGUMENT^#)
|
||||
init(newValue) {
|
||||
}
|
||||
get { 1 }
|
||||
}
|
||||
// SECOND_INITIALIZES_ARGUMENT: Begin completions, 2 items
|
||||
// SECOND_INITIALIZES_ARGUMENT-DAG: Keyword/None: accesses: [#Specify stored properties accessed by the accessor#];
|
||||
// SECOND_INITIALIZES_ARGUMENT-DAG: Decl[InstanceVar]/CurrNominal: storedProperty[#Int#];
|
||||
}
|
||||
|
||||
struct TestArgument {
|
||||
var other: Int
|
||||
|
||||
var otherComputed: Int { 1 }
|
||||
|
||||
func testFunc() {}
|
||||
|
||||
var firstInitializesArgument: Int {
|
||||
@storageRestrictions(initializes: #^FIRST_INITIALIZES_ARGUMENT?check=FIRST_ARGUMENT^#)
|
||||
init(newValue) {
|
||||
}
|
||||
get { 1 }
|
||||
}
|
||||
// FIRST_ARGUMENT: Begin completions, 1 item
|
||||
// FIRST_ARGUMENT-DAG: Decl[InstanceVar]/CurrNominal: other[#Int#];
|
||||
|
||||
var firstAccessesArgument: Int {
|
||||
@storageRestrictions(initializes: #^FIRST_ACCESSES_ARGUMENT?check=FIRST_ARGUMENT^#)
|
||||
init(newValue) {
|
||||
}
|
||||
get { 1 }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user