[CodeCompletion] Add completion for @storageRestrictions

This commit is contained in:
Alex Hoppen
2023-09-18 16:50:21 -07:00
parent 8577c6b0a3
commit ee85314e6f
5 changed files with 263 additions and 43 deletions

View File

@@ -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);

View File

@@ -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.

View File

@@ -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);
}
}
}
}
}

View File

@@ -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

View 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 }
}
}