-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[CodeCompletion] Avoid type checking all top-level decls in the primary file #27981
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
[CodeCompletion] Avoid type checking all top-level decls in the primary file #27981
Conversation
4ffb312
to
883cbd4
Compare
lib/IDE/CodeCompletion.cpp
Outdated
auto Access = VD->getFormalAccess(); | ||
// Use the greater access between the protocol requirement and the witness. | ||
// In case of: | ||
// | ||
// public protocol P { func foo() } | ||
// public class B { func foo() {} } | ||
// public class C: B, P { | ||
// <complete> | ||
// } | ||
// | ||
// 'VD' is 'B.foo()' which is implicitly 'internal'. But as the overriding | ||
// declaration, the user needs to write both 'public' and 'override': | ||
// | ||
// public class C: B { | ||
// public override func foo() {} | ||
// } | ||
if (Access < AccessLevel::Public && | ||
!isa<ProtocolDecl>(VD->getDeclContext())) { | ||
for (auto Conformance : CurrentNominal->getAllConformances()) { | ||
auto Proto = Conformance->getProtocol(); | ||
for (auto Member : Proto->getMembers()) { | ||
auto Requirement = dyn_cast<ValueDecl>(Member); | ||
if (!Requirement || isa<AssociatedTypeDecl>(Requirement)) | ||
continue; | ||
|
||
auto Witness = Conformance->getWitnessDecl(Requirement); | ||
if (Witness == VD) | ||
Access = std::max(Access, Requirement->getFormalAccess()); | ||
} | ||
} | ||
} |
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.
Previously B.foo()
is overwriteAccess(AccessLevel::Public)
ed from diagnoseWitnessFixAccessLevel()
. Since the typechecking is now in DiagnosticSuppression
, diagnoseWitnessFixAccessLevel()
is not called so we need to manually check it.
lib/IDE/CodeCompletion.cpp
Outdated
return; | ||
|
||
// A 'class' member with an initial value cannot be overriden either. | ||
if (D->isStatic() && isa<VarDecl>(D) && cast<VarDecl>(D)->hasInitialValue()) |
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.
HasInitialValueAttr
is injected in the typechecking.
@@ -5096,6 +5135,9 @@ void CodeCompletionCallbacksImpl::doneParsing() { | |||
CurDeclContext, | |||
CurDeclContext->getASTContext().SourceMgr.getCodeCompletionLoc()); | |||
|
|||
// Add keywords even if type checking fails completely. | |||
addKeywords(CompletionContext.getResultSink(), MaybeFuncBody); |
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.
Moved after typeCheckContextUntil()
because addKeywords()
may call DeclContext::getSelfProtocolDecl()
which can only be called after bindExtensions()
.
@swift-ci Please smoke test |
f96a0cd
to
9f9298e
Compare
@swift-ci Please smoke test |
@swift-ci Please test Linux |
Build failed |
@swift-ci Please test Linux |
9f9298e
to
0d6e0c8
Compare
@swift-ci Please smoke test |
@@ -483,6 +477,58 @@ static void | |||
lookupTypeMembers(BaseTy, PT, Consumer, CurrDC, LS, Reason); | |||
} | |||
|
|||
/// Trigger synthesizing implicit member declarations to make them "visible". | |||
static void synthesizeMemberDeclsForLookup(NominalTypeDecl *NTD, |
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.
How is this triggered during normal type-checking? This seems kind of brittle to live in lookup-visible-decls, although admittedly this whole file is basically like that...
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 the type is declared in the primary files, they are synthesized in TypeChecker::typeCheckDecl()
(DeclChecker
) or in typeCheckFunctionsAndExternalDecls()
.
But if it's in the non-primary files, AFAIU, they are lazily synthesized on demand in DeclContext::lookupQualified()
https://github.com/apple/swift/blob/bd971507bae5a12a2b40365e795799037ec59fcc/lib/AST/NameLookup.cpp#L1573-L1579
https://github.com/apple/swift/blob/bd971507bae5a12a2b40365e795799037ec59fcc/lib/Sema/TypeChecker.h#L1018-L1024
I can move this synthesizeMemberDeclsForLookup()
function into TypeChecker::
, but I don't think it's worth...
0d6e0c8
to
fc2a4d6
Compare
Resolved conflicts. |
lib/IDE/CodeCompletion.cpp
Outdated
if (D->isFinal()) | ||
return; | ||
|
||
// A 'class' member with an initial value cannot be overriden either. |
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 sure I understand. Can you give an example? Normally stored properties can be overridden with computed properties as long as they're not final.
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 believe this meant something like:
class C {
class var foo = 1
}
But that's compile error, so we don't care much how they are treated in override completion. So maybe this check is not needed.
But here, I'm just changing D->getAttrs().hasAttribute<HasInitialValueAttr>()
to isa<VarDecl>(D) && cast<VarDecl>(D)->hasInitialValue()
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.
My point was that the presence of an initial value expression does not have any bearing on whether the declaration can be overridden or not. The comment above is incorrect.
I think the only relevant check here is whether the property is final (static
is equivalent to class final
).
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.
Right, I just removed this check.
lib/IDE/CodeCompletion.cpp
Outdated
|
||
auto Witness = Conformance->getWitnessDecl(Requirement); | ||
if (Witness == VD) | ||
Access = std::max(Access, Requirement->getFormalAccess()); |
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.
The access of the requirement is the same as the access of the protocol, so I believe this entire loop and the search for the witness of 'VD' can go away.
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 mean like this?
if (Access < AccessLevel::Public &&
!isa<ProtocolDecl>(VD->getDeclContext())) {
for (auto Conformance : CurrentNominal->getAllConformances()) {
auto Proto = Conformance->getProtocol();
Access = std::max(Access, Proto->getFormalAccess());
}
}
This may cause unnecessary public
when the type conforms to two or more protocols. Like
public protocol P1 { }
protocol P2 {
func bar()
}
public class B {
func bar() {}
}
public class C: B, P1, P2 {
<complete>
}
In this case when overriding bar()
, C.bar()
is not needed to be public
.
lib/IDE/ExprContextAnalysis.cpp
Outdated
// protocols in the AST. | ||
(void) DC->getASTContext().getStdlibModule(); | ||
|
||
bindExtensions(*DC->getParentSourceFile()); |
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.
Maybe the getStdlibModule() and bindExtensions() calls should be done as part of performParseAndNameBindingOnly()?
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.
Made bindExtensions()
a part of performParseAndNameBindingOnly()
.
It turned out that getStdlibModule()
is meaningless. Since ASTContext::getStdlibModule(bool loadIfAbsent = false)
is called with default loadIfAbsent = false
, it doesn't do anything. So I just removed it from performTypechecking()
lib/Sema/LookupVisibleDecls.cpp
Outdated
Conformance->getRootConformance()); | ||
if (!NormalConformance) | ||
continue; | ||
for (auto Member : Proto->getMembers()) { |
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 can replace this loop with calls to Conformance->forEachTypeWitness()
and forEachValueWitness()
, passing in useResolver=true
.
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.
Done.
lib/Sema/LookupVisibleDecls.cpp
Outdated
for (auto Member : IDC->getMembers()) { | ||
if (auto var = dyn_cast<VarDecl>(Member)) { | ||
if (var->hasAttachedPropertyWrapper()) { | ||
auto sourceFile = var->getDeclContext()->getParentSourceFile(); |
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 can check if the IDC itself is a member of a source file outside the loop.
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, done!
lib/Sema/LookupVisibleDecls.cpp
Outdated
const DeclContext *DC) { | ||
// Synthesize the memberwise initializer for structs or default initializer | ||
// for classes. | ||
if (!NTD->hasInterfaceType()) |
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.
It's possible that an interface type has been set, but implicit constructors were not added (and vice versa). You can unconditinally call addImplicitConstructors()
here; it's idempotent if called multiple times.
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.
IIRC, that causes circular dependency error. I will try that again.
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.
Here's the cyclic dependency error when enabling typo correction. How can we fix this?
$ cat test.swift
struct Broken {
var b = True
}
$ swift -frontend -typecheck -typo-correction-limit 10 -debug-cycles test.swift
test.swift:1:8: error: circular reference
struct Broken {
^
test.swift:2:3: note: through reference here
var b = True
^
test.swift:2:7: note: through reference here
var b = True
^
test.swift:2:7: note: through reference here
var b = True
^
===CYCLE DETECTED===
`--SynthesizeMemberwiseInitRequest(test.(file)[email protected]:1:8)
`--ParseMembersRequest(test.(file)[email protected]:1:8) -> {(unknown decl), test.(file)[email protected]:2:7}
`--StorageImplInfoRequest(test.(file)[email protected]:2:7)
| `--AttachedPropertyWrappersRequest(test.(file)[email protected]:2:7)
`--StorageImplInfoRequest(test.(file)[email protected]:2:7)
| `--AttachedPropertyWrappersRequest(test.(file)[email protected]:2:7)
`--AccessLevelRequest(test.(file)[email protected]:2:7)
`--AccessLevelRequest(test.(file)[email protected]:2:7)
`--InterfaceTypeRequest(test.(file)[email protected]:2:7)
`--NamingPatternRequest(test.(file)[email protected]:2:7)
`--PatternBindingEntryRequest((unknown decl), 0)
`--AttachedPropertyWrappersRequest(test.(file)[email protected]:2:7)
...
`--ParseMembersRequest(test.(file)[email protected]:1:8) -> {(unknown decl), test.(file)[email protected]:2:7}
`--GenericParamListRequest(test.(file)[email protected]:1:8)
`--GenericSignatureRequest(test.(file)[email protected]:1:8)
| `--GenericParamListRequest(test.(file)[email protected]:1:8)
`--GenericParamListRequest(test.(file)[email protected]:1:8)
`--InterfaceTypeRequest(test.(file)[email protected]:1:8)
| `--GenericParamListRequest(test.(file)[email protected]:1:8)
| `--GenericParamListRequest(test.(file)[email protected]:1:8)
`--HasMemberwiseInitRequest(test.(file)[email protected]:1:8) -> 1
| `--InterfaceTypeRequest(test.(file)[email protected]:1:8)
| | `--GenericParamListRequest(test.(file)[email protected]:1:8)
| | `--GenericParamListRequest(test.(file)[email protected]:1:8)
| `--HasUserDefinedDesignatedInitRequest(test.(file)[email protected]:1:8) -> 0
| | `--ParseMembersRequest(test.(file)[email protected]:1:8) -> {(unknown decl), test.(file)[email protected]:2:7}
| `--ParseMembersRequest(test.(file)[email protected]:1:8) -> {(unknown decl), test.(file)[email protected]:2:7}
| `--StorageImplInfoRequest(test.(file)[email protected]:2:7)
| | `--AttachedPropertyWrappersRequest(test.(file)[email protected]:2:7)
| `--StorageImplInfoRequest(test.(file)[email protected]:2:7)
| | `--AttachedPropertyWrappersRequest(test.(file)[email protected]:2:7)
| `--AccessLevelRequest(test.(file)[email protected]:2:7)
`--SynthesizeMemberwiseInitRequest(test.(file)[email protected]:1:8) (cyclic dependency)
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 see. It might be better then to check to explicitly check if an SynthesizeMemberwiseInitRequest is active, instead of using hasInterfaceType()
, ie,
VD->getASTContext().evaluator.hasActiveRequest(
SynthesizeMemberwiseInitRequest{...})
lib/Sema/LookupVisibleDecls.cpp
Outdated
resolver->resolveTypeWitness(NormalConformance, ATD); | ||
} else if (auto *VD = dyn_cast<ValueDecl>(Member)) { | ||
if (!NormalConformance->hasWitness(VD)) | ||
resolver->resolveWitness(NormalConformance, VD); |
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 we do this all this inside lookupDeclsFromProtocolsBeingConformedTo() instead?
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 don't think so because this has to be done before lookupTypeMembers()
which is called before lookupDeclsFromProtocolsBeingConformedTo()
.
lib/Sema/LookupVisibleDecls.cpp
Outdated
}; | ||
synthesizePropertyWrappers(NTD); | ||
for (auto ED : NTD->getExtensions()) | ||
synthesizePropertyWrappers(ED); |
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 might be cleaner to visit the extension's members in doGlobalExtensionLookup()
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.
Done
024b1f6
to
113ea63
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.
I don't have much to add to Slava and Ben's feedback, but I love the form of this. It's great to see us taking advantage of more laziness in the type checker.
lib/IDE/CodeCompletion.cpp
Outdated
Conformance->getRootConformance()->forEachValueWitness( | ||
[&](ValueDecl *req, Witness witness) { | ||
if (witness.getDecl() == VD) | ||
Access = std::max(Access, req->getFormalAccess()); |
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 still think it's better to write Conformance->getProtocol()->getFormalAccess()
here.
lib/IDE/CodeCompletion.cpp
Outdated
if (D->isFinal()) | ||
return; | ||
|
||
// A 'class' member with an initial value cannot be overriden either. |
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.
My point was that the presence of an initial value expression does not have any bearing on whether the declaration can be overridden or not. The comment above is incorrect.
I think the only relevant check here is whether the property is final (static
is equivalent to class final
).
lib/Sema/LookupVisibleDecls.cpp
Outdated
const DeclContext *DC) { | ||
// Synthesize the memberwise initializer for structs or default initializer | ||
// for classes. | ||
if (!NTD->hasInterfaceType()) |
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 see. It might be better then to check to explicitly check if an SynthesizeMemberwiseInitRequest is active, instead of using hasInterfaceType()
, ie,
VD->getASTContext().evaluator.hasActiveRequest(
SynthesizeMemberwiseInitRequest{...})
- Default memberwise initializer for struct was not suggested - Dynamic member lookup didn't work - `init(rawValue:)` was not suggested for `RawRepresentable` types - '$name' was not suggested for `@propertyWrapper`ed value rdar://problem/56391233
… file - Use `performParseAndResolveImportsOnly()` to invoke the frontend - Do `bindExtensions()` in `ide::typeCheckContextUntil()` - Typecheck preceding `TopLevelCodeDecl`s only if the compleiton is in a `TopLevelCodeDecl` - Other related tweaks rdar://problem/56636747
113ea63
to
5c2b6a5
Compare
@swift-ci Please smoke test |
In override completion, we didn't use to emit 'class var' with the initial expression. However, having initial expression does not have any bearing on whether the declaration is overridable or not.
5c2b6a5
to
97b16ce
Compare
@swift-ci Please smoke test |
The first commit is purely bug fixes. They are revealed if the second commit is enabled. However they can be reproduced if the declaration is in the other files in the current module. (Added
test/IDE/complete_multifile.swift
regression test case).init(rawValue:)
was not suggested forRawRepresentable
types (rdar://problem/56391233)@propertyWrapper
ed valueThe second commit actually stops eager typechecking decls in the primary file.
performParseAndResolveImportsOnly()
to invoke the frontendbindExtensions()
inide::typeCheckContextUntil()
TopLevelCodeDecl
s only if the compleiton is ina
TopLevelCodeDecl
rdar://problem/56636747