-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[CodeCompletion] Explicitly support enum pattern matching #38627
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
d808efb
to
6263d21
Compare
@swift-ci Please smoke test |
lib/IDE/CodeCompletion.cpp
Outdated
@@ -4665,6 +4677,21 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { | |||
/*includeProtocolExtensionMembers*/true); | |||
} | |||
|
|||
/// Complete all enum members declared on \p T. | |||
void getUnresolvedEnumElementCompletions(Type T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Patterns for enum cases are called EnumElementPattern
. So I think getEnumElementPatternCompletions()
is more correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You 👍ed. So are you renaming this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Good catch. I changed it at some point but that rename must have gotten lost in the refactoring. Renamed it again.
lib/Parse/ParsePattern.cpp
Outdated
@@ -1333,6 +1333,20 @@ ParserResult<Pattern> Parser::parseMatchingPattern(bool isExprBasic) { | |||
// matching-pattern ::= expr | |||
// Fall back to expression parsing for ambiguous forms. Name lookup will | |||
// disambiguate. | |||
if (Tok.is(tok::period_prefix) && peekToken().is(tok::code_complete)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, I think this doesn't cover case .foo(.#^HERE^#)
. Could you make it work in nested cases too?
Also considering case .foo(.#^HERE^#)
case, we don't know foo
is a static function or an enum element. If it's a static function, we can only use ~=
because enum element patterns can't be nested in an expression pattern. Can we differentiate it in sawSolution()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
case .foo(.#^HERE^#)
works because the parser correctly parses the .foo
as an enum element pattern, so we don’t need to worry about it. I’ll add test cases for it.
include/swift/IDE/CodeCompletion.h
Outdated
@@ -533,6 +533,9 @@ enum class CompletionKind { | |||
None, | |||
Import, | |||
UnresolvedMember, | |||
/// An unresolved member completion in a pattern matching position. | |||
/// E.g. \code if case .#^COMPLETE^# = x {} \endcode | |||
PatternMatching, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of this naming. Could you include something that imply "after dot" and "without base expr/pattern"? For example PatternAfterDotWithoutBase
or UnresolvedDotPattern
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to UnresolvedDotPattern
.
lib/Sema/TypeCheckCodeCompletion.cpp
Outdated
addTypeToResultVectorIfNecessary(Results.ExprPatternTypes, | ||
CompletionExprType); | ||
|
||
if (auto MatchVar = PatternMatching->getMatchVar()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we derive MatchVar
or MatchingVarType
from Solution
? If not, could you make it so?
If we can do that, I think we don't need to add a dedicated completion kind. Instead, we can detect it in UnresolvedMemberTypeCheckCompletionCallback::sawSolution()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not easily, I think. We could maybe search through the constraint locators for one that declares a variable named $match
, but that feels awfully ad-hoc to me and potentially fragile if $match
somehow leaks to nested scopes.
Also note that we only offer enum cases for the type of the match var, so UnresolvedMemberTypeCheckCompletionCallback
would need to keep track of the match var types separately from the current Results
.
We could merge PatternMatchingTypeCheckCompletionCallback
and UnresolvedMemberTypeCheckCompletionCallback
so that UnresolvedMemberTypeCheckCompletionCallback
also has ExprPattern *PatternMatching
and SmallVector<Type, 1> EnumPatternTypes
members, which can be nullptr
/empty if we are not completing in a pattern matching location. I’m not sure if I like that better though… WDYT?
6263d21
to
1096697
Compare
@swift-ci Please smoke test |
6eea740
to
5ecc2c9
Compare
@rintaro helpfully noted that the previous implementation didn’t support nested enum pattern matching, e.g. in the following, we weren’t suggesting enum PlainEnum {
case foo(Int)
}
enum WithNestedEnum {
case bar(PlainEnum)
}
func completeInNestedPatternMatching(x: WithNestedEnum) {
switch x {
case .bar(.#^COMPLETE^#)
} I remodeled the code completion to not be a custom completion kind anymore (like @rintaro suggested earlier). Instead, we discover if the code completion is inside a pattern matching location (by looking if it’s an argument to an implicit |
@swift-ci Please test |
TupleExpr *ArgTuple = | ||
dyn_cast_or_null<TupleExpr>(CS.getParentExpr(CompletionExpr)); | ||
if (!ArgTuple || !ArgTuple->isImplicit() || ArgTuple->getNumElements() != 2) { | ||
return nullptr; | ||
} | ||
|
||
auto Binary = dyn_cast_or_null<BinaryExpr>(CS.getParentExpr(ArgTuple)); | ||
if (!Binary || !Binary->isImplicit()) { | ||
return nullptr; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably conflicts with #38836 cc: @hamishknight
lib/IDE/CodeCompletion.cpp
Outdated
@@ -4665,6 +4675,21 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { | |||
/*includeProtocolExtensionMembers*/true); | |||
} | |||
|
|||
/// Complete all enum members declared on \p T. | |||
void getUnresolvedEnumElementCompletions(Type T) { | |||
if (!T->mayHaveMembers()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it makes sense to check T->getAnyNominal()
is EnumDecl
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a reasonable and easy performance improvement. 👍
74a8c0d
to
3770c5f
Compare
@swift-ci Please smoke test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementation LGTM except a couple of comments.
Please cooperate with Hamish when landing this.
lib/IDE/CodeCompletion.cpp
Outdated
@@ -4694,6 +4704,21 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { | |||
/*includeProtocolExtensionMembers*/true); | |||
} | |||
|
|||
/// Complete all enum members declared on \p T. | |||
void getUnresolvedEnumElementCompletions(Type T) { | |||
if (!T->mayHaveMembers() || !T->getAnyNominal() || !isa<EnumDecl>(T->getAnyNominal())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
T->getAnyNominal() == true
implies T->mayHaveMembers()
so
if (!T->mayHaveMembers() || !T->getAnyNominal() || !isa<EnumDecl>(T->getAnyNominal())) | |
if (!llvm::isa_and_nonnull<EnumDecl>(T->getAnyNominal())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imported isa_and_nonnull
into swift
namespace in #39078 so just
if (!T->mayHaveMembers() || !T->getAnyNominal() || !isa<EnumDecl>(T->getAnyNominal())) | |
if (!isa_and_nonnull<EnumDecl>(T->getAnyNominal())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. I didn’t know that existed. Thanks.
lib/IDE/CodeCompletion.cpp
Outdated
@@ -4665,6 +4677,21 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { | |||
/*includeProtocolExtensionMembers*/true); | |||
} | |||
|
|||
/// Complete all enum members declared on \p T. | |||
void getUnresolvedEnumElementCompletions(Type T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You 👍ed. So are you renaming this?
Pattern matching in Swift can either be expression pattern matching by comparing two instances using the `~=` operator or using enum matching by matching the enum case and its associated types (+ tuple pattern matching, but that’s not relevant here). We currenlty only consider the expression pattern matching case for code completion. To provide enum pattern matching results, we thus need to have a `~=` operator between the code completion token and the match expression For example, when we are completing ```swift enum MyEnum { case myCase(String) } switch x { case .#^COMPLETE^# } ``` then we are looking up all overloads of `~=` and try to match it to the call arguments `(<Code Completion Type>, MyEnum)`. The way we currently get `#^COMPLETE^#` to offer members of `MyEnum`, is that we are trying to make sure that the `~=<T: Equatable>(T, T)` operator defined in the standard library is the best solution even though it has fixes associated with it. For that we need to carefully make sure to ignore other, more favourable overloads of `~=` in `filterSolutions` so that `~=<T: Equatable>(T, T)` has the best score. This poses several problems: - If the user defines a custom overload of `~=` that we don't prune when filtering solutions (e.g. `func ~=(pattern: OtherType, value: MyEnum) -> Bool`), it gets a better score than `~=<T: Equatable>(T, T)` and thus we only offer members of `OtherType` instead of members from `MyEnum` - We are also suggesting static members of `MyEnum`, even though we can't pattern match them due to the lack of the `~=` operator. If we detect that the completion expression is in a pattern matching position, also suggests all enum members of the matched type. This allows us to remove the hack which deliberately ignores certain overloads of `~=` since we no longer rely on `~=<T: Equatable>(T, T)`. It thus provides correct results in both of the above cases. Fixes rdar://77263334 [SR-14547]
3770c5f
to
12ff361
Compare
@swift-ci Please smoke test |
Pattern matching in Swift can either be expression pattern matching by comparing two instances using the
~=
operator or using enum matching by matching the enum case and its associated types (+ tuple pattern matching, but that’s not relevant here). We currenlty only consider the expression pattern matching case for code completion. To provide enum pattern matching results, we thus need to have a~=
operator between the code completion token and the match expressionFor example, when we are completing
then we are looking up all overloads of
~=
and try to match it to the call arguments(<Code Completion Type>, MyEnum)
.The way we currently get
#^COMPLETE^#
to offer members ofMyEnum
, is that we are trying to make sure that the~=<T: Equatable>(T, T)
operator defined in the standard library is the best solution even though it has fixes associated with it. For that we need to carefully make sure to ignore other, more favourable overloads of~=
infilterSolutions
so that~=<T: Equatable>(T, T)
has the best score.This poses several problems:
~=
that we don't prune when filtering solutions (e.g.func ~=(pattern: OtherType, value: MyEnum) -> Bool
), it gets a better score than~=<T: Equatable>(T, T)
and thus we only offer members ofOtherType
instead of members fromMyEnum
MyEnum
, even though we can't pattern match them due to the lack of the~=
operator.If we detect that the completion expression is in a pattern matching position, also suggests all enum members of the matched type. This allows us to remove the hack which deliberately ignores certain overloads of
~=
since we no longer rely on~=<T: Equatable>(T, T)
. It thus provides correct results in both of the above cases.Fixes rdar://77263334 [SR-14547]