mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[CodeComplete] More efficient skipping for completions in if/switch exprs
Skip type-checking multi-statement branches if the completion is in a single-expression branch, and skip type-checking the expression as a whole if the completion is in a multi-statement branch.
This commit is contained in:
@@ -310,6 +310,10 @@ public:
|
|||||||
/// SingleValueStmtExpr.
|
/// SingleValueStmtExpr.
|
||||||
bool isForSingleValueStmtConjunction() const;
|
bool isForSingleValueStmtConjunction() const;
|
||||||
|
|
||||||
|
/// Whether this locator identifies a conjunction for the branches of a
|
||||||
|
/// SingleValueStmtExpr, or a conjunction for one of the BraceStmts itself.
|
||||||
|
bool isForSingleValueStmtConjunctionOrBrace() const;
|
||||||
|
|
||||||
/// Whether this locator identifies a conversion for a SingleValueStmtExpr
|
/// Whether this locator identifies a conversion for a SingleValueStmtExpr
|
||||||
/// branch, and if so, the kind of branch.
|
/// branch, and if so, the kind of branch.
|
||||||
llvm::Optional<SingleValueStmtBranchKind> isForSingleValueStmtBranch() const;
|
llvm::Optional<SingleValueStmtBranchKind> isForSingleValueStmtBranch() const;
|
||||||
|
|||||||
@@ -883,11 +883,15 @@ bool ConjunctionStep::attempt(const ConjunctionElement &element) {
|
|||||||
assert(!ModifiedOptions.has_value() &&
|
assert(!ModifiedOptions.has_value() &&
|
||||||
"Previously modified options should have been restored in resume");
|
"Previously modified options should have been restored in resume");
|
||||||
if (CS.isForCodeCompletion() &&
|
if (CS.isForCodeCompletion() &&
|
||||||
!element.mightContainCodeCompletionToken(CS)) {
|
!element.mightContainCodeCompletionToken(CS) &&
|
||||||
|
!getLocator()->isForSingleValueStmtConjunctionOrBrace()) {
|
||||||
ModifiedOptions.emplace(CS.Options);
|
ModifiedOptions.emplace(CS.Options);
|
||||||
// If we know that this conjunction element doesn't contain the code
|
// If we know that this conjunction element doesn't contain the code
|
||||||
// completion token, type check it in normal mode without any special
|
// completion token, type check it in normal mode without any special
|
||||||
// behavior that is intended for the code completion token.
|
// behavior that is intended for the code completion token.
|
||||||
|
// Avoid doing this for SingleValueStmtExprs, because we can more eagerly
|
||||||
|
// prune branches in that case, which requires us to detect the code
|
||||||
|
// completion option while solving the conjunction.
|
||||||
CS.Options -= ConstraintSystemFlags::ForCodeCompletion;
|
CS.Options -= ConstraintSystemFlags::ForCodeCompletion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,9 @@ private:
|
|||||||
// MARK: Constraint generation
|
// MARK: Constraint generation
|
||||||
|
|
||||||
/// Check whether it makes sense to convert this element into a constraint.
|
/// Check whether it makes sense to convert this element into a constraint.
|
||||||
static bool isViableElement(ASTNode element) {
|
static bool isViableElement(ASTNode element,
|
||||||
|
bool isForSingleValueStmtCompletion,
|
||||||
|
ConstraintSystem &cs) {
|
||||||
if (auto *decl = element.dyn_cast<Decl *>()) {
|
if (auto *decl = element.dyn_cast<Decl *>()) {
|
||||||
// - Ignore variable declarations, they are handled by pattern bindings;
|
// - Ignore variable declarations, they are handled by pattern bindings;
|
||||||
// - Ignore #if, the chosen children should appear in the
|
// - Ignore #if, the chosen children should appear in the
|
||||||
@@ -255,10 +257,21 @@ static bool isViableElement(ASTNode element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (auto *stmt = element.dyn_cast<Stmt *>()) {
|
if (auto *stmt = element.dyn_cast<Stmt *>()) {
|
||||||
// Empty brace statements are now viable because they do not require
|
|
||||||
// inference.
|
|
||||||
if (auto *braceStmt = dyn_cast<BraceStmt>(stmt)) {
|
if (auto *braceStmt = dyn_cast<BraceStmt>(stmt)) {
|
||||||
return braceStmt->getNumElements() > 0;
|
// Empty brace statements are not viable because they do not require
|
||||||
|
// inference.
|
||||||
|
if (braceStmt->empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Skip if we're doing completion for a SingleValueStmtExpr, and have a
|
||||||
|
// brace that doesn't involve a single expression, and doesn't have a
|
||||||
|
// code completion token, as it won't contribute to the type of the
|
||||||
|
// SingleValueStmtExpr.
|
||||||
|
if (isForSingleValueStmtCompletion &&
|
||||||
|
!braceStmt->getSingleActiveExpression() &&
|
||||||
|
!cs.containsIDEInspectionTarget(braceStmt)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,13 +337,19 @@ static void createConjunction(ConstraintSystem &cs,
|
|||||||
|
|
||||||
VarRefCollector paramCollector(cs);
|
VarRefCollector paramCollector(cs);
|
||||||
|
|
||||||
|
// Whether we're doing completion, and the conjunction is for a
|
||||||
|
// SingleValueStmtExpr, or one of its braces.
|
||||||
|
const auto isForSingleValueStmtCompletion =
|
||||||
|
cs.isForCodeCompletion() &&
|
||||||
|
locator->isForSingleValueStmtConjunctionOrBrace();
|
||||||
|
|
||||||
for (const auto &entry : elements) {
|
for (const auto &entry : elements) {
|
||||||
ASTNode element = std::get<0>(entry);
|
ASTNode element = std::get<0>(entry);
|
||||||
ContextualTypeInfo context = std::get<1>(entry);
|
ContextualTypeInfo context = std::get<1>(entry);
|
||||||
bool isDiscarded = std::get<2>(entry);
|
bool isDiscarded = std::get<2>(entry);
|
||||||
ConstraintLocator *elementLoc = std::get<3>(entry);
|
ConstraintLocator *elementLoc = std::get<3>(entry);
|
||||||
|
|
||||||
if (!isViableElement(element))
|
if (!isViableElement(element, isForSingleValueStmtCompletion, cs))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// If this conjunction going to represent a body of a closure,
|
// If this conjunction going to represent a body of a closure,
|
||||||
|
|||||||
@@ -634,22 +634,52 @@ bool ConstraintLocator::isForMacroExpansion() const {
|
|||||||
return directlyAt<MacroExpansionExpr>();
|
return directlyAt<MacroExpansionExpr>();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintLocator::isForSingleValueStmtConjunction() const {
|
static bool isForSingleValueStmtConjunction(ASTNode anchor,
|
||||||
auto *SVE = getAsExpr<SingleValueStmtExpr>(getAnchor());
|
ArrayRef<LocatorPathElt> path) {
|
||||||
|
auto *SVE = getAsExpr<SingleValueStmtExpr>(anchor);
|
||||||
if (!SVE)
|
if (!SVE)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Ignore a trailing SyntacticElement path element for the statement.
|
if (!path.empty()) {
|
||||||
auto path = getPath();
|
// Ignore a trailing SyntacticElement path element for the statement.
|
||||||
if (auto elt = getLastElementAs<LocatorPathElt::SyntacticElement>()) {
|
if (auto elt = path.back().getAs<LocatorPathElt::SyntacticElement>()) {
|
||||||
if (elt->getElement() == ASTNode(SVE->getStmt()))
|
if (elt->getElement() == ASTNode(SVE->getStmt()))
|
||||||
path = path.drop_back();
|
path = path.drop_back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other than the trailing SyntaticElement, we must be at the anchor.
|
// Other than the trailing SyntaticElement, we must be at the anchor.
|
||||||
return path.empty();
|
return path.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ConstraintLocator::isForSingleValueStmtConjunction() const {
|
||||||
|
return ::isForSingleValueStmtConjunction(getAnchor(), getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConstraintLocator::isForSingleValueStmtConjunctionOrBrace() const {
|
||||||
|
if (!isExpr<SingleValueStmtExpr>(getAnchor()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto path = getPath();
|
||||||
|
while (!path.empty()) {
|
||||||
|
// Ignore a trailing TernaryBranch locator, which is used for if statements.
|
||||||
|
if (path.back().is<LocatorPathElt::TernaryBranch>()) {
|
||||||
|
path = path.drop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore a SyntaticElement path element for a case statement of a switch.
|
||||||
|
if (auto elt = path.back().getAs<LocatorPathElt::SyntacticElement>()) {
|
||||||
|
if (elt->getElement().isStmt(StmtKind::Case)) {
|
||||||
|
path = path.drop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ::isForSingleValueStmtConjunction(getAnchor(), path);
|
||||||
|
}
|
||||||
|
|
||||||
llvm::Optional<SingleValueStmtBranchKind>
|
llvm::Optional<SingleValueStmtBranchKind>
|
||||||
ConstraintLocator::isForSingleValueStmtBranch() const {
|
ConstraintLocator::isForSingleValueStmtBranch() const {
|
||||||
// Ignore a trailing ContextualType path element.
|
// Ignore a trailing ContextualType path element.
|
||||||
|
|||||||
@@ -2526,10 +2526,25 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
|
|||||||
if (isa<TapExpr>(E))
|
if (isa<TapExpr>(E))
|
||||||
return Action::SkipChildren(E);
|
return Action::SkipChildren(E);
|
||||||
|
|
||||||
// Don't walk into SingleValueStmtExprs, they should be type-checked as
|
// If the location is within a single-expression branch of a
|
||||||
// a whole.
|
// SingleValueStmtExpr, walk it directly rather than as part of the brace.
|
||||||
if (isa<SingleValueStmtExpr>(E))
|
// This ensures we type-check it a part of the whole expression, unless it
|
||||||
return Action::SkipChildren(E);
|
// has an inner closure or SVE, in which case we can still pick up a
|
||||||
|
// better node to type-check.
|
||||||
|
if (auto *SVE = dyn_cast<SingleValueStmtExpr>(E)) {
|
||||||
|
SmallVector<Expr *> scratch;
|
||||||
|
for (auto *branch : SVE->getSingleExprBranches(scratch)) {
|
||||||
|
auto branchCharRange = Lexer::getCharSourceRangeFromSourceRange(
|
||||||
|
SM, branch->getSourceRange());
|
||||||
|
if (branchCharRange.contains(Loc)) {
|
||||||
|
if (!branch->walk(*this))
|
||||||
|
return Action::Stop();
|
||||||
|
|
||||||
|
return Action::SkipChildren(E);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Action::Continue(E);
|
||||||
|
}
|
||||||
|
|
||||||
if (auto closure = dyn_cast<ClosureExpr>(E)) {
|
if (auto closure = dyn_cast<ClosureExpr>(E)) {
|
||||||
// NOTE: When a client wants to type check a closure signature, it
|
// NOTE: When a client wants to type check a closure signature, it
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
// RUN: %empty-directory(%t)
|
// RUN: %empty-directory(%t)
|
||||||
// RUN: %target-swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t
|
// RUN: %target-swift-ide-test -batch-code-completion -source-filename %s -debug-forbid-typecheck-prefix FORBIDDEN -filecheck %raw-FileCheck -completion-output-dir %t
|
||||||
|
|
||||||
enum E {
|
enum E {
|
||||||
case e
|
case e
|
||||||
case f(Int)
|
case f(Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SomeError: Error {}
|
||||||
|
|
||||||
func ifExprDotReturn() -> E {
|
func ifExprDotReturn() -> E {
|
||||||
if .random() {
|
if .random() {
|
||||||
.#^DOT1?check=DOT^#
|
.#^DOT1?check=DOT^#
|
||||||
@@ -113,6 +115,227 @@ func switchExprBinding() -> E {
|
|||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Triggering interface type computation of this will cause an assert to fire,
|
||||||
|
// ensuring we don't attempt to type-check any references to it.
|
||||||
|
let NOTYPECHECK = FORBIDDEN
|
||||||
|
|
||||||
|
func testSkipTypechecking1() -> E {
|
||||||
|
// Make sure we don't try to type-check the first branch, as it doesn't
|
||||||
|
// contribute to the type.
|
||||||
|
if .random() {
|
||||||
|
_ = NOTYPECHECK
|
||||||
|
throw SomeError()
|
||||||
|
} else {
|
||||||
|
.#^DOT11?check=DOT^#
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypechecking2() -> E {
|
||||||
|
// Make sure we don't try to type-check the second branch, as it doesn't
|
||||||
|
// contribute to the type.
|
||||||
|
if .random() {
|
||||||
|
.#^DOT12?check=DOT^#
|
||||||
|
} else {
|
||||||
|
_ = NOTYPECHECK
|
||||||
|
throw SomeError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypechecking3() -> E {
|
||||||
|
// Make sure we don't try to type-check the first branch, as it doesn't
|
||||||
|
// contribute to the type.
|
||||||
|
switch Bool.random() {
|
||||||
|
case true:
|
||||||
|
_ = NOTYPECHECK
|
||||||
|
throw SomeError()
|
||||||
|
case false:
|
||||||
|
.#^DOT13?check=DOT^#
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypechecking4() -> E {
|
||||||
|
// Make sure we don't try to type-check the second branch, as it doesn't
|
||||||
|
// contribute to the type.
|
||||||
|
switch Bool.random() {
|
||||||
|
case true:
|
||||||
|
.#^DOT14?check=DOT^#
|
||||||
|
case false:
|
||||||
|
_ = NOTYPECHECK
|
||||||
|
throw SomeError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func takesE(_ e: E) {}
|
||||||
|
|
||||||
|
func testSkipTypechecking5() throws -> E {
|
||||||
|
// Make sure we only type-check the first branch.
|
||||||
|
if .random() {
|
||||||
|
// We can still skip type-checking this tho.
|
||||||
|
x = NOTYPECHECK
|
||||||
|
|
||||||
|
takesE(.#^DOT15?check=DOT^#)
|
||||||
|
throw SomeError()
|
||||||
|
} else {
|
||||||
|
NOTYPECHECK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypechecking6() throws -> E {
|
||||||
|
// Make sure we only type-check the first branch.
|
||||||
|
switch Bool.random() {
|
||||||
|
case true:
|
||||||
|
// We can still skip type-checking this tho.
|
||||||
|
x = NOTYPECHECK
|
||||||
|
|
||||||
|
takesE(.#^DOT16?check=DOT^#)
|
||||||
|
throw SomeError()
|
||||||
|
case false:
|
||||||
|
NOTYPECHECK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum F {
|
||||||
|
case x
|
||||||
|
}
|
||||||
|
|
||||||
|
func takesIntOrDouble(_ i: Int) -> E { E.e }
|
||||||
|
func takesIntOrDouble(_ i: Double) -> F { F.x }
|
||||||
|
|
||||||
|
func testSkipTypechecking7() throws -> E {
|
||||||
|
let fn = {
|
||||||
|
switch Bool.random() {
|
||||||
|
case true:
|
||||||
|
.#^DOT17?check=DOT^#
|
||||||
|
case false:
|
||||||
|
// Make sure we don't end up with an ambiguity here.
|
||||||
|
takesIntOrDouble(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypeChecking8() throws -> E {
|
||||||
|
let e: E = if Bool.random() {
|
||||||
|
takesE(.#^DOT18?check=DOT^#)
|
||||||
|
throw NOTYPECHECK
|
||||||
|
} else {
|
||||||
|
NOTYPECHECK
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypeChecking8() throws -> E {
|
||||||
|
// Only need to type-check the inner function in this case.
|
||||||
|
let e: E = if Bool.random() {
|
||||||
|
func localFunc() {
|
||||||
|
takesE(.#^DOT19?check=DOT^#)
|
||||||
|
}
|
||||||
|
throw NOTYPECHECK
|
||||||
|
} else {
|
||||||
|
NOTYPECHECK
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func takesArgAndClosure<T>(_ x: Int, fn: () -> T) -> T { fatalError() }
|
||||||
|
|
||||||
|
func testSkipTypeChecking9() -> E {
|
||||||
|
// We need to type-check everything for this.
|
||||||
|
if Bool.random() {
|
||||||
|
.e
|
||||||
|
} else {
|
||||||
|
takesArgAndClosure(0) {
|
||||||
|
.#^DOT20?check=DOT^#
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypeChecking10() -> E {
|
||||||
|
// We only need to type-check the inner-most function for this.
|
||||||
|
if Bool.random() {
|
||||||
|
NOTYPECHECK
|
||||||
|
} else {
|
||||||
|
takesArgAndClosure(NOTYPECHECK) {
|
||||||
|
func foo() {
|
||||||
|
takesE(.#^DOT21?check=DOT^#)
|
||||||
|
}
|
||||||
|
return NOTYPECHECK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypeChecking11() -> E {
|
||||||
|
// We only need to type-check the inner-most function for this.
|
||||||
|
if Bool.random() {
|
||||||
|
NOTYPECHECK
|
||||||
|
} else {
|
||||||
|
if NOTYPECHECK {
|
||||||
|
func foo() {
|
||||||
|
takesE(.#^DOT22?check=DOT^#)
|
||||||
|
}
|
||||||
|
throw NOTYPECHECK
|
||||||
|
} else {
|
||||||
|
NOTYPECHECK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypeChecking12() -> E {
|
||||||
|
// Only need to type-check the condition here.
|
||||||
|
if .#^DOT23?check=BOOL^# {
|
||||||
|
NOTYPECHECK
|
||||||
|
} else {
|
||||||
|
NOTYPECHECK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOOL: Begin completions
|
||||||
|
// BOOL-DAG: Decl[Constructor]/CurrNominal/IsSystem/TypeRelation[Convertible]: init()[#Bool#]; name=init()
|
||||||
|
// BOOL-DAG: Decl[StaticMethod]/CurrNominal/Flair[ExprSpecific]/IsSystem/TypeRelation[Convertible]: random()[#Bool#]; name=random()
|
||||||
|
|
||||||
|
func testSkipTypeChecking13(_ e: E) -> E {
|
||||||
|
// TODO: Currently this requires type-checking the bodies, but we ought to
|
||||||
|
// be able to skip them. This is also an issue in closures.
|
||||||
|
switch e {
|
||||||
|
case .#^DOT24?check=DOT^#:
|
||||||
|
.e
|
||||||
|
default:
|
||||||
|
.e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypeChecking14() -> E {
|
||||||
|
if Bool.random() {
|
||||||
|
.#^DOT25?check=DOT^#
|
||||||
|
} else if .random() {
|
||||||
|
let x = NOTYPECHECK
|
||||||
|
throw x
|
||||||
|
} else if .random() {
|
||||||
|
let x = NOTYPECHECK
|
||||||
|
throw x
|
||||||
|
} else {
|
||||||
|
let x = NOTYPECHECK
|
||||||
|
throw x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipTypeChecking14() -> E {
|
||||||
|
switch Bool.random() {
|
||||||
|
case true:
|
||||||
|
.#^DOT26?check=DOT^#
|
||||||
|
case _ where Bool.random():
|
||||||
|
let x = NOTYPECHECK
|
||||||
|
throw x
|
||||||
|
case _ where Bool.random():
|
||||||
|
let x = NOTYPECHECK
|
||||||
|
throw x
|
||||||
|
default:
|
||||||
|
let x = NOTYPECHECK
|
||||||
|
throw x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// DOT: Begin completions, 2 items
|
// DOT: Begin completions, 2 items
|
||||||
// DOT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: e[#E#]; name=e
|
// DOT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: e[#E#]; name=e
|
||||||
// DOT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: f({#Int#})[#E#]; name=f()
|
// DOT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: f({#Int#})[#E#]; name=f()
|
||||||
|
|||||||
Reference in New Issue
Block a user