Merge pull request #21060 from rjmccall/global-observer-crash-5.0
[5.0] Don't crash when modifying a global observed variable
diff --git a/include/swift/Sema/IDETypeChecking.h b/include/swift/Sema/IDETypeChecking.h
index 1f2c057..c55dbce 100644
--- a/include/swift/Sema/IDETypeChecking.h
+++ b/include/swift/Sema/IDETypeChecking.h
@@ -98,19 +98,16 @@
Expr *&parsedExpr,
ConcreteDeclRef &referencedDecl);
- /// Typecheck the sequence expression \p parsedExpr for code completion.
+ /// Resolve type of operator function with \c opName appending it to \c LHS.
///
- /// This requires that \p parsedExpr is a SequenceExpr and that it contains:
- /// * ... leading sequence LHS
- /// * UnresolvedDeclRefExpr operator
- /// * CodeCompletionExpr RHS
- ///
- /// On success, returns false, and replaces parsedExpr with the binary
- /// expression corresponding to the operator. The type of the operator and
- /// RHS are also set, but the rest of the expression may not be typed
- ///
- /// The LHS should already be type-checked or this will be very slow.
- bool typeCheckCompletionSequence(DeclContext *DC, Expr *&parsedExpr);
+ /// For \p refKind, use \c DeclRefKind::PostfixOperator for postfix operator,
+ /// or \c DeclRefKind::BinaryOperator for infix operator.
+ /// On success, returns resolved function type of the operator. The LHS should
+ /// already be type-checked. This function guarantees LHS not to be modified.
+ FunctionType *getTypeOfCompletionOperator(DeclContext *DC, Expr *LHS,
+ Identifier opName,
+ DeclRefKind refKind,
+ ConcreteDeclRef &referencedDecl);
/// Typecheck the given expression.
bool typeCheckExpression(DeclContext *DC, Expr *&parsedExpr);
diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp
index 94f06d3..415e6c3 100644
--- a/lib/IDE/CodeCompletion.cpp
+++ b/lib/IDE/CodeCompletion.cpp
@@ -3291,35 +3291,15 @@
}
void tryPostfixOperator(Expr *expr, PostfixOperatorDecl *op) {
- auto Ty = expr->getType();
- if (!Ty)
+ ConcreteDeclRef referencedDecl;
+ FunctionType *funcTy = getTypeOfCompletionOperator(
+ const_cast<DeclContext *>(CurrDeclContext), expr, op->getName(),
+ DeclRefKind::PostfixOperator, referencedDecl);
+ if (!funcTy)
return;
- SWIFT_DEFER {
- // Restore type.
- // FIXME: This is workaround for getTypeOfExpressionWithoutApplying()
- // modifies type of 'expr'.
- expr->setType(Ty);
- prepareForRetypechecking(expr);
- };
-
- // We allocate these expressions on the stack because we know they can't
- // escape and there isn't a better way to allocate scratch Expr nodes.
- UnresolvedDeclRefExpr UDRE(op->getName(), DeclRefKind::PostfixOperator,
- DeclNameLoc(expr->getSourceRange().End));
- ParenExpr parenExpr(expr->getSourceRange().Start, expr,
- expr->getSourceRange().End,
- /*hasTrailingClosure=*/false);
- PostfixUnaryExpr opExpr(&UDRE, &parenExpr);
- Expr *tempExpr = &opExpr;
- ConcreteDeclRef referencedDecl;
- if (auto T = getTypeOfCompletionContextExpr(
- CurrDeclContext->getASTContext(),
- const_cast<DeclContext *>(CurrDeclContext),
- CompletionTypeCheckKind::Normal,
- tempExpr,
- referencedDecl))
- addPostfixOperatorCompletion(op, *T);
+ // TODO: Use referencedDecl (FuncDecl) instead of 'op' (OperatorDecl).
+ addPostfixOperatorCompletion(op, funcTy->getResult());
}
void addAssignmentOperator(Type RHSType, Type resultType) {
@@ -3366,169 +3346,67 @@
addTypeAnnotation(builder, resultType);
}
- void tryInfixOperatorCompletion(InfixOperatorDecl *op, SequenceExpr *SE) {
- if (op->getName().str() == "~>")
+ void tryInfixOperatorCompletion(Expr *foldedExpr, InfixOperatorDecl *op) {
+ ConcreteDeclRef referencedDecl;
+ FunctionType *funcTy = getTypeOfCompletionOperator(
+ const_cast<DeclContext *>(CurrDeclContext), foldedExpr, op->getName(),
+ DeclRefKind::BinaryOperator, referencedDecl);
+ if (!funcTy)
return;
- MutableArrayRef<Expr *> sequence = SE->getElements();
- assert(sequence.size() >= 3 && !sequence.back() &&
- !sequence.drop_back(1).back() && "sequence not cleaned up");
- assert((sequence.size() & 1) && "sequence expr ending with operator");
+ Type lhsTy = funcTy->getParams()[0].getPlainType();
+ Type rhsTy = funcTy->getParams()[1].getPlainType();
+ Type resultTy = funcTy->getResult();
- // FIXME: these checks should apply to the LHS of the operator, not the
- // immediately left expression. Move under the type-checking.
- Expr *LHS = sequence.drop_back(2).back();
- if (LHS->getType() && (LHS->getType()->is<MetatypeType>() ||
- LHS->getType()->is<AnyFunctionType>()))
- return;
-
- // Preserve LHS type for restoring it.
- Type LHSTy = LHS->getType();
-
- // We allocate these expressions on the stack because we know they can't
- // escape and there isn't a better way to allocate scratch Expr nodes.
- UnresolvedDeclRefExpr UDRE(op->getName(), DeclRefKind::BinaryOperator,
- DeclNameLoc(LHS->getEndLoc()));
- sequence.drop_back(1).back() = &UDRE;
- CodeCompletionExpr CCE(LHS->getSourceRange());
- sequence.back() = &CCE;
-
- SWIFT_DEFER {
- // Reset sequence.
- SE->setElement(SE->getNumElements() - 1, nullptr);
- SE->setElement(SE->getNumElements() - 2, nullptr);
- LHS->setType(LHSTy);
- prepareForRetypechecking(SE);
-
- for (auto &element : sequence.drop_back(2)) {
- // Unfold expressions for re-typechecking sequence.
- if (auto *assignExpr = dyn_cast_or_null<AssignExpr>(element)) {
- assignExpr->setSrc(nullptr);
- assignExpr->setDest(nullptr);
- } else if (auto *ifExpr = dyn_cast_or_null<IfExpr>(element)) {
- ifExpr->setCondExpr(nullptr);
- ifExpr->setElseExpr(nullptr);
- }
-
- // Reset any references to operators in types, so they are properly
- // handled as operators by sequence folding.
- //
- // FIXME: Would be better to have some kind of 'OperatorRefExpr'?
- if (auto operatorRef = element->getMemberOperatorRef()) {
- operatorRef->setType(nullptr);
- element = operatorRef;
- }
- }
- };
-
- Expr *expr = SE;
- if (!typeCheckCompletionSequence(const_cast<DeclContext *>(CurrDeclContext),
- expr)) {
- if (!LHS->getType() ||
- !LHS->getType()->getRValueType()->getOptionalObjectType()) {
- // Don't complete optional operators on non-optional types.
- // FIXME: can we get the type-checker to disallow these for us?
- if (op->getName().str() == "??")
- return;
- if (auto NT = CCE.getType()->getNominalOrBoundGenericNominal()) {
- if (NT->getName() ==
- CurrDeclContext->getASTContext().Id_OptionalNilComparisonType)
- return;
- }
- }
-
- // If the right-hand side and result type are both type parameters, we're
- // not providing a useful completion.
- if (expr->getType()->isTypeParameter() &&
- CCE.getType()->isTypeParameter())
+ // Don't complete optional operators on non-optional types.
+ if (!lhsTy->getRValueType()->getOptionalObjectType()) {
+ // 'T ?? T'
+ if (op->getName().str() == "??")
return;
-
- addInfixOperatorCompletion(op, expr->getType(), CCE.getType());
+ // 'T == nil'
+ if (auto NT = rhsTy->getNominalOrBoundGenericNominal())
+ if (NT->getName() ==
+ CurrDeclContext->getASTContext().Id_OptionalNilComparisonType)
+ return;
}
+
+ // If the right-hand side and result type are both type parameters, we're
+ // not providing a useful completion.
+ if (resultTy->isTypeParameter() && rhsTy->isTypeParameter())
+ return;
+
+ // TODO: Use referencedDecl (FuncDecl) instead of 'op' (OperatorDecl).
+ addInfixOperatorCompletion(op, funcTy->getResult(),
+ funcTy->getParams()[1].getPlainType());
}
- void flattenBinaryExpr(Expr *expr, SmallVectorImpl<Expr *> &sequence) {
- if (auto binExpr = dyn_cast<BinaryExpr>(expr)) {
- flattenBinaryExpr(binExpr->getArg()->getElement(0), sequence);
- sequence.push_back(binExpr->getFn());
- flattenBinaryExpr(binExpr->getArg()->getElement(1), sequence);
- } else if (auto assignExpr = dyn_cast<AssignExpr>(expr)) {
- flattenBinaryExpr(assignExpr->getDest(), sequence);
- sequence.push_back(assignExpr);
- flattenBinaryExpr(assignExpr->getSrc(), sequence);
- assignExpr->setDest(nullptr);
- assignExpr->setSrc(nullptr);
- } else if (auto ifExpr = dyn_cast<IfExpr>(expr)) {
- flattenBinaryExpr(ifExpr->getCondExpr(), sequence);
- sequence.push_back(ifExpr);
- flattenBinaryExpr(ifExpr->getElseExpr(), sequence);
- ifExpr->setCondExpr(nullptr);
- ifExpr->setElseExpr(nullptr);
- } else if (auto tryExpr = dyn_cast<AnyTryExpr>(expr)) {
- // Strip out try expression. It doesn't affect completion.
- flattenBinaryExpr(tryExpr->getSubExpr(), sequence);
- } else if (auto optEval = dyn_cast<OptionalEvaluationExpr>(expr)){
- // Strip out optional evaluation expression. It doesn't affect completion.
- flattenBinaryExpr(optEval->getSubExpr(), sequence);
- } else {
- sequence.push_back(expr);
- }
- }
+ Expr *typeCheckLeadingSequence(Expr *LHS, ArrayRef<Expr *> leadingSequence) {
+ if (leadingSequence.empty())
+ return LHS;
- void typeCheckLeadingSequence(SmallVectorImpl<Expr *> &sequence) {
-
- // Strip out try and optional evaluation expr because foldSequence() mutates
- // hierarchy of these expressions. They don't affect completion anyway.
- for (auto &element : sequence) {
- if (auto *tryExpr = dyn_cast<AnyTryExpr>(element))
- element = tryExpr->getSubExpr();
- if (auto *optEval = dyn_cast<OptionalEvaluationExpr>(element))
- element = optEval->getSubExpr();
- }
+ assert(leadingSequence.size() % 2 == 0);
+ SmallVector<Expr *, 3> sequence(leadingSequence.begin(),
+ leadingSequence.end());
+ sequence.push_back(LHS);
Expr *expr =
SequenceExpr::create(CurrDeclContext->getASTContext(), sequence);
prepareForRetypechecking(expr);
- // Take advantage of the fact the type-checker leaves the types on the AST.
if (!typeCheckExpression(const_cast<DeclContext *>(CurrDeclContext),
expr)) {
- // Rebuild the sequence from the type-checked version.
- sequence.clear();
- flattenBinaryExpr(expr, sequence);
- return;
+ return expr;
}
-
- // Fall back to just using the immediate LHS.
- auto LHS = sequence.back();
- sequence.clear();
- sequence.push_back(LHS);
+ return LHS;
}
void getOperatorCompletions(Expr *LHS, ArrayRef<Expr *> leadingSequence) {
- std::vector<OperatorDecl *> operators = collectOperators();
+ Expr *foldedExpr = typeCheckLeadingSequence(LHS, leadingSequence);
+ std::vector<OperatorDecl *> operators = collectOperators();
// FIXME: this always chooses the first operator with the given name.
llvm::DenseSet<Identifier> seenPostfixOperators;
llvm::DenseSet<Identifier> seenInfixOperators;
- SmallVector<Expr *, 3> sequence(leadingSequence.begin(),
- leadingSequence.end());
- sequence.push_back(LHS);
- assert((sequence.size() & 1) && "sequence expr ending with operator");
-
- if (sequence.size() > 1)
- typeCheckLeadingSequence(sequence);
-
- // Retrieve typechecked LHS.
- LHS = sequence.back();
-
- // Create a single sequence expression, which we will modify for each
- // operator, filling in the operator and dummy right-hand side.
- sequence.push_back(nullptr); // operator
- sequence.push_back(nullptr); // RHS
- auto *SE = SequenceExpr::create(CurrDeclContext->getASTContext(), sequence);
- prepareForRetypechecking(SE);
-
for (auto op : operators) {
switch (op->getKind()) {
case DeclKind::PrefixOperator:
@@ -3541,7 +3419,7 @@
break;
case DeclKind::InfixOperator:
if (seenInfixOperators.insert(op->getName()).second)
- tryInfixOperatorCompletion(cast<InfixOperatorDecl>(op), SE);
+ tryInfixOperatorCompletion(foldedExpr, cast<InfixOperatorDecl>(op));
break;
default:
llvm_unreachable("unexpected operator kind");
diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp
index 16cf4cd..8e7159d 100644
--- a/lib/Sema/CSGen.cpp
+++ b/lib/Sema/CSGen.cpp
@@ -127,14 +127,20 @@
class LinkedExprCollector : public ASTWalker {
llvm::SmallVectorImpl<Expr*> &LinkedExprs;
+ ConstraintSystem &CS;
public:
-
- LinkedExprCollector(llvm::SmallVectorImpl<Expr*> &linkedExprs) :
- LinkedExprs(linkedExprs) {}
-
+ LinkedExprCollector(llvm::SmallVectorImpl<Expr *> &linkedExprs,
+ ConstraintSystem &cs)
+ : LinkedExprs(linkedExprs), CS(cs) {}
+
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
-
+
+ if (CS.shouldReusePrecheckedType() &&
+ !CS.getType(expr)->hasTypeVariable()) {
+ return { false, expr };
+ }
+
// Store top-level binary exprs for further analysis.
if (isa<BinaryExpr>(expr) ||
@@ -195,6 +201,11 @@
LTI(lti), CS(cs) {}
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
+
+ if (CS.shouldReusePrecheckedType() &&
+ !CS.getType(expr)->hasTypeVariable()) {
+ return { false, expr };
+ }
if (isa<IntegerLiteralExpr>(expr)) {
LTI.haveIntLiteral = true;
@@ -934,6 +945,11 @@
CS(cs) {}
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
+
+ if (CS.shouldReusePrecheckedType() &&
+ !CS.getType(expr)->hasTypeVariable()) {
+ return { false, expr };
+ }
if (auto applyExpr = dyn_cast<ApplyExpr>(expr)) {
if (isa<PrefixUnaryExpr>(applyExpr) ||
@@ -3240,6 +3256,12 @@
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
while (true) {
+
+ // If we should reuse pre-checked types, don't sanitize the expression
+ // if it's already type-checked.
+ if (CS.shouldReusePrecheckedType() && expr->getType())
+ return { false, expr };
+
// OpenExistentialExpr contains OpaqueValueExpr in its sub expression.
if (auto OOE = dyn_cast<OpenExistentialExpr>(expr)) {
auto archetypeVal = OOE->getOpaqueValue();
@@ -3409,6 +3431,15 @@
ConstraintWalker(ConstraintGenerator &CG) : CG(CG) { }
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
+
+ if (CG.getConstraintSystem().shouldReusePrecheckedType()) {
+ if (expr->getType()) {
+ assert(!expr->getType()->hasTypeVariable());
+ CG.getConstraintSystem().cacheType(expr);
+ return { false, expr };
+ }
+ }
+
// Note that the subexpression of a #selector expression is
// unevaluated.
if (auto sel = dyn_cast<ObjCSelectorExpr>(expr)) {
@@ -3629,7 +3660,7 @@
SmallVector<Expr *, 16> linkedExprs;
// Collect any linked expressions.
- LinkedExprCollector collector(linkedExprs);
+ LinkedExprCollector collector(linkedExprs, *this);
e->walk(collector);
// Favor types, as appropriate.
diff --git a/lib/Sema/ConstraintSystem.h b/lib/Sema/ConstraintSystem.h
index 93fd294..1b419a6 100644
--- a/lib/Sema/ConstraintSystem.h
+++ b/lib/Sema/ConstraintSystem.h
@@ -823,6 +823,10 @@
/// system is not applied to the expression AST, but the ConstraintSystem is
/// left in-tact.
AllowUnresolvedTypeVariables = 0x10,
+
+ /// If set, constraint system always reuses type of pre-typechecked
+ /// expression, and doesn't dig into its subexpressions.
+ ReusePrecheckedType = 0x20,
};
/// Options that affect the constraint system as a whole.
@@ -1779,17 +1783,21 @@
public:
/// \brief Whether we should attempt to fix problems.
- bool shouldAttemptFixes() {
+ bool shouldAttemptFixes() const {
if (!(Options & ConstraintSystemFlags::AllowFixes))
return false;
return !solverState || solverState->recordFixes;
}
- bool shouldSuppressDiagnostics() {
+ bool shouldSuppressDiagnostics() const {
return Options.contains(ConstraintSystemFlags::SuppressDiagnostics);
}
+ bool shouldReusePrecheckedType() const {
+ return Options.contains(ConstraintSystemFlags::ReusePrecheckedType);
+ }
+
/// \brief Log and record the application of the fix. Return true iff any
/// subsequent solution would be worse than the best known solution.
bool recordFix(ConstraintFix *fix);
diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp
index 1410e8e..4e7365a 100644
--- a/lib/Sema/TypeCheckConstraints.cpp
+++ b/lib/Sema/TypeCheckConstraints.cpp
@@ -2220,57 +2220,26 @@
}
}
-bool TypeChecker::typeCheckCompletionSequence(Expr *&expr, DeclContext *DC) {
- FrontendStatsTracer StatsTracer(Context.Stats, "typecheck-completion-seq", expr);
+static FunctionType *
+getTypeOfCompletionOperatorImpl(TypeChecker &TC, DeclContext *DC, Expr *expr,
+ ConcreteDeclRef &referencedDecl) {
+ ASTContext &Context = TC.Context;
+
+ FrontendStatsTracer StatsTracer(Context.Stats,
+ "typecheck-completion-operator", expr);
PrettyStackTraceExpr stackTrace(Context, "type-checking", expr);
+ ConstraintSystemOptions options;
+ options |= ConstraintSystemFlags::SuppressDiagnostics;
+ options |= ConstraintSystemFlags::ReusePrecheckedType;
+
// Construct a constraint system from this expression.
- ConstraintSystem CS(*this, DC, ConstraintSystemFlags::SuppressDiagnostics);
+ ConstraintSystem CS(TC, DC, options);
+ expr = CS.generateConstraints(expr);
+ if (!expr)
+ return nullptr;
- auto *SE = cast<SequenceExpr>(expr);
- assert(SE->getNumElements() >= 3);
- auto *op = SE->getElement(SE->getNumElements() - 2);
- auto *CCE = cast<CodeCompletionExpr>(SE->getElements().back());
-
- // Resolve the op.
- op = resolveDeclRefExpr(cast<UnresolvedDeclRefExpr>(op), DC);
- SE->setElement(SE->getNumElements() - 2, op);
-
- // Fold the sequence.
- expr = foldSequence(SE, DC);
-
- // Find the code-completion expression and operator again.
- Expr *exprAsBinOp = nullptr;
- while (true) {
- Expr *RHS;
- if (auto *binExpr = dyn_cast<BinaryExpr>(expr))
- RHS = binExpr->getArg()->getElement(1);
- else if (auto *assignExpr = dyn_cast<AssignExpr>(expr))
- RHS = assignExpr->getSrc();
- else if (auto *ifExpr = dyn_cast<IfExpr>(expr))
- RHS = ifExpr->getElseExpr();
- else
- break;
-
- if (RHS == CCE) {
- exprAsBinOp = expr;
- break;
- }
- expr = RHS;
- }
- if (!exprAsBinOp)
- return true;
-
- // Ensure the output expression is up to date.
- assert(exprAsBinOp == expr && isa<BinaryExpr>(expr) && "found wrong expr?");
-
- if (auto generated = CS.generateConstraints(expr)) {
- expr = generated;
- } else {
- return true;
- }
-
- if (getLangOpts().DebugConstraintSolver) {
+ if (TC.getLangOpts().DebugConstraintSolver) {
auto &log = Context.TypeCheckerDebug->getStream();
log << "---Initial constraints for the given expression---\n";
expr->dump(log);
@@ -2281,20 +2250,100 @@
// Attempt to solve the constraint system.
SmallVector<Solution, 4> viable;
if (CS.solve(expr, viable, FreeTypeVariableBinding::Disallow))
- return true;
+ return nullptr;
auto &solution = viable[0];
- if (getLangOpts().DebugConstraintSolver) {
+ if (TC.getLangOpts().DebugConstraintSolver) {
auto &log = Context.TypeCheckerDebug->getStream();
log << "---Solution---\n";
solution.dump(log);
}
- auto &solutionCS = solution.getConstraintSystem();
- expr->setType(solution.simplifyType(solutionCS.getType(expr)));
- auto completionType = solution.simplifyType(solutionCS.getType(CCE));
- CCE->setType(completionType);
- return false;
+ // Fill the results.
+ Expr *opExpr = cast<ApplyExpr>(expr)->getFn();
+ referencedDecl =
+ solution.resolveLocatorToDecl(CS.getConstraintLocator(opExpr));
+
+ // Return '(ArgType[, ArgType]) -> ResultType' as a function type.
+ // We don't use the type of the operator expression because we want the types
+ // of the *arguments* instead of the types of the parameters.
+ Expr *argsExpr = cast<ApplyExpr>(expr)->getArg();
+ SmallVector<FunctionType::Param, 2> argTypes;
+ if (auto *PE = dyn_cast<ParenExpr>(argsExpr)) {
+ argTypes.emplace_back(solution.simplifyType(CS.getType(PE->getSubExpr())));
+ } else if (auto *TE = dyn_cast<TupleExpr>(argsExpr)) {
+ for (auto arg : TE->getElements())
+ argTypes.emplace_back(solution.simplifyType(CS.getType(arg)));
+ }
+
+ return FunctionType::get(argTypes, solution.simplifyType(CS.getType(expr)));
+}
+
+/// \brief Return the type of operator function for specified LHS, or a null
+/// \c Type on error.
+FunctionType *
+TypeChecker::getTypeOfCompletionOperator(DeclContext *DC, Expr *LHS,
+ Identifier opName, DeclRefKind refKind,
+ ConcreteDeclRef &referencedDecl) {
+
+ // For the infix operator, find the actual LHS from pre-folded LHS.
+ if (refKind == DeclRefKind::BinaryOperator)
+ LHS = findLHS(DC, LHS, opName);
+
+ if (!LHS)
+ return nullptr;
+
+ auto LHSTy = LHS->getType();
+
+ // FIXME: 'UnresolvedType' still might be typechecked by an operator.
+ if (!LHSTy || LHSTy->is<UnresolvedType>())
+ return nullptr;
+
+ // Meta types and function types cannot be a operand of operator expressions.
+ if (LHSTy->is<MetatypeType>() || LHSTy->is<AnyFunctionType>())
+ return nullptr;
+
+ auto Loc = LHS->getEndLoc();
+
+ // Build temporary expression to typecheck.
+ // We allocate these expressions on the stack because we know they can't
+ // escape and there isn't a better way to allocate scratch Expr nodes.
+ UnresolvedDeclRefExpr UDRE(opName, refKind, DeclNameLoc(Loc));
+ auto *opExpr = resolveDeclRefExpr(&UDRE, DC);
+
+ switch (refKind) {
+
+ case DeclRefKind::PostfixOperator: {
+ // (postfix_unary_expr
+ // (declref_expr name=<opName>)
+ // (paren_expr
+ // (<LHS>)))
+ ParenExpr Args(SourceLoc(), LHS, SourceLoc(),
+ /*hasTrailingClosure=*/false);
+ PostfixUnaryExpr postfixExpr(opExpr, &Args);
+ return getTypeOfCompletionOperatorImpl(*this, DC, &postfixExpr,
+ referencedDecl);
+ }
+
+ case DeclRefKind::BinaryOperator: {
+ // (binary_expr
+ // (declref_expr name=<opName>)
+ // (tuple_expr
+ // (<LHS>)
+ // (code_completion_expr)))
+ CodeCompletionExpr dummyRHS(Loc);
+ auto Args = TupleExpr::create(
+ Context, SourceLoc(), {LHS, &dummyRHS}, {}, {}, SourceLoc(),
+ /*hasTrailingClosure=*/false, /*isImplicit=*/true);
+ BinaryExpr binaryExpr(opExpr, Args, /*isImplicit=*/true);
+
+ return getTypeOfCompletionOperatorImpl(*this, DC, &binaryExpr,
+ referencedDecl);
+ }
+
+ default:
+ llvm_unreachable("Invalid DeclRefKind for operator completion");
+ }
}
bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,
diff --git a/lib/Sema/TypeCheckExpr.cpp b/lib/Sema/TypeCheckExpr.cpp
index 15786b8..33e86e1 100644
--- a/lib/Sema/TypeCheckExpr.cpp
+++ b/lib/Sema/TypeCheckExpr.cpp
@@ -190,6 +190,15 @@
return lookupPrecedenceGroupForInfixOperator(DC, binaryExpr->getFn());
}
+ if (auto *DSCE = dyn_cast<DotSyntaxCallExpr>(E)) {
+ return lookupPrecedenceGroupForInfixOperator(DC, DSCE->getFn());
+ }
+
+ if (auto *MRE = dyn_cast<MemberRefExpr>(E)) {
+ Identifier name = MRE->getDecl().getDecl()->getBaseName().getIdentifier();
+ return lookupPrecedenceGroupForOperator(*this, DC, name, MRE->getLoc());
+ }
+
// If E is already an ErrorExpr, then we've diagnosed it as invalid already,
// otherwise emit an error.
if (!isa<ErrorExpr>(E))
@@ -198,6 +207,58 @@
return nullptr;
}
+/// Find LHS as if we append binary operator to existing pre-folded expresion.
+/// Returns found expression, or \c nullptr if the operator is not applicable.
+///
+/// For example, given '(== R (* A B))':
+/// 'findLHS(DC, expr, "+")' returns '(* A B)'.
+/// 'findLHS(DC, expr, "<<")' returns 'B'.
+/// 'findLHS(DC, expr, '==')' returns nullptr.
+Expr *TypeChecker::findLHS(DeclContext *DC, Expr *E, Identifier name) {
+ auto right = lookupPrecedenceGroupForOperator(*this, DC, name, E->getEndLoc());
+ while (true) {
+
+ // Look through implicit conversions.
+ if (auto ICE = dyn_cast<ImplicitConversionExpr>(E)) {
+ E = ICE->getSyntacticSubExpr();
+ continue;
+ }
+ if (auto ACE = dyn_cast<AutoClosureExpr>(E)) {
+ E = ACE->getSingleExpressionBody();
+ continue;
+ }
+
+ auto left = lookupPrecedenceGroupForInfixOperator(DC, E);
+ if (!left)
+ // LHS is not binary expression.
+ return E;
+ switch (Context.associateInfixOperators(left, right)) {
+ case swift::Associativity::None:
+ return nullptr;
+ case swift::Associativity::Left:
+ return E;
+ case swift::Associativity::Right:
+ break;
+ }
+ // Find the RHS of the current binary expr.
+ if (auto *assignExpr = dyn_cast<AssignExpr>(E)) {
+ E = assignExpr->getSrc();
+ } else if (auto *ifExpr = dyn_cast<IfExpr>(E)) {
+ E = ifExpr->getElseExpr();
+ } else if (auto *binaryExpr = dyn_cast<BinaryExpr>(E)) {
+ auto *Args = dyn_cast<TupleExpr>(binaryExpr->getArg());
+ if (!Args || Args->getNumElements() != 2)
+ return nullptr;
+ E = Args->getElement(1);
+ } else {
+ // E.g. 'fn() as Int << 2'.
+ // In this case '<<' has higher precedence than 'as', but the LHS should
+ // be 'fn() as Int' instead of 'Int'.
+ return E;
+ }
+ }
+}
+
// The way we compute isEndOfSequence relies on the assumption that
// the sequence-folding algorithm never recurses with a prefix of the
// entire sequence.
diff --git a/lib/Sema/TypeChecker.cpp b/lib/Sema/TypeChecker.cpp
index d514cbb..5cbc12e 100644
--- a/lib/Sema/TypeChecker.cpp
+++ b/lib/Sema/TypeChecker.cpp
@@ -843,11 +843,17 @@
referencedDecl);
}
-bool swift::typeCheckCompletionSequence(DeclContext *DC, Expr *&parsedExpr) {
+/// \brief Return the type of operator function for specified LHS, or a null
+/// \c Type on error.
+FunctionType *
+swift::getTypeOfCompletionOperator(DeclContext *DC, Expr *LHS,
+ Identifier opName, DeclRefKind refKind,
+ ConcreteDeclRef &referencedDecl) {
auto &ctx = DC->getASTContext();
DiagnosticSuppression suppression(ctx.Diags);
TypeChecker &TC = createTypeChecker(ctx);
- return TC.typeCheckCompletionSequence(parsedExpr, DC);
+ return TC.getTypeOfCompletionOperator(DC, LHS, opName, refKind,
+ referencedDecl);
}
bool swift::typeCheckExpression(DeclContext *DC, Expr *&parsedExpr) {
diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h
index ff6f076..8ee715b 100644
--- a/lib/Sema/TypeChecker.h
+++ b/lib/Sema/TypeChecker.h
@@ -1404,7 +1404,12 @@
FreeTypeVariableBinding::Disallow,
ExprTypeCheckListener *listener = nullptr);
- bool typeCheckCompletionSequence(Expr *&expr, DeclContext *DC);
+ /// \brief Return the type of operator function for specified LHS, or a null
+ /// \c Type on error.
+ FunctionType *getTypeOfCompletionOperator(DeclContext *DC, Expr *LHS,
+ Identifier opName,
+ DeclRefKind refKind,
+ ConcreteDeclRef &referencedDecl);
/// Check the key-path expression.
///
@@ -1885,6 +1890,10 @@
PrecedenceGroupDecl *lookupPrecedenceGroup(DeclContext *dc, Identifier name,
SourceLoc nameLoc);
+ /// Given an pre-folded expression, find LHS from the expression if a binary
+ /// operator \c name appended to the expression.
+ Expr *findLHS(DeclContext *DC, Expr *E, Identifier name);
+
/// \brief Look up the Bool type in the standard library.
Type lookupBoolType(const DeclContext *dc);
diff --git a/test/IDE/complete_from_stdlib.swift b/test/IDE/complete_from_stdlib.swift
index daf976d..b7aad66 100644
--- a/test/IDE/complete_from_stdlib.swift
+++ b/test/IDE/complete_from_stdlib.swift
@@ -282,7 +282,7 @@
}
// INFIX_EXT_STRING: Begin completions
// INFIX_EXT_STRING-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: + {#String#}[#String#]
-// INFIX_EXT_STRING-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: == {#Bool#}[#Bool#]
// INFIX_EXT_STRING-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: || {#Bool#}[#Bool#]
// INFIX_EXT_STRING-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: && {#Bool#}[#Bool#]
+// INFIX_EXT_STRING-NOT: ==
// INFIX_EXT_STRING: End completions
diff --git a/test/IDE/complete_operators.swift b/test/IDE/complete_operators.swift
index 2e54a61..5b53fb8 100644
--- a/test/IDE/complete_operators.swift
+++ b/test/IDE/complete_operators.swift
@@ -145,15 +145,20 @@
// ===--- Infix operators
+precedencegroup S2PrecedenceGroup {
+ associativity: left
+ lowerThan: ComparisonPrecedence
+ higherThan: AssignmentPrecedence
+}
+precedencegroup S2AssignmentPrecedenceGroup {
+ associativity: none
+ lowerThan: ComparisonPrecedence
+ higherThan: AssignmentPrecedence
+}
+
struct S2 {}
-infix operator ** {
- associativity left
- precedence 123
-}
-infix operator **= {
- associativity none
- precedence 123
-}
+infix operator ** : S2PrecedenceGroup
+infix operator **= : S2AssignmentPrecedenceGroup
func +(x: S2, y: S2) -> S2 { return x }
func **(x: S2, y: Int) -> S2 { return x }
func **=(x: inout S2, y: Int) -> Void { return x }
@@ -364,13 +369,13 @@
x + x == x + x#^EXT_INFIX_2^#
}
// S4_EXT_INFIX: Begin completions
-// S4_EXT_INFIX-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: != {#Bool#}[#Bool#]
// S4_EXT_INFIX-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: + {#S4#}[#S4#]
-// S4_EXT_INFIX-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: == {#Bool#}[#Bool#]
// S4_EXT_INFIX-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: && {#Bool#}[#Bool#]
// S4_EXT_INFIX-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: || {#Bool#}[#Bool#]
// S4_EXT_INFIX: End completions
+// S4_EXT_INFIX-NEG-NOT: !=
+// S4_EXT_INFIX-NEG-NOT: ==
// S4_EXT_INFIX_NEG-NOT: +++
// S4_EXT_INFIX_NEG-NOT: &&&
@@ -425,4 +430,4 @@
// INFIX_AUTOCLOSURE_1-DAG: Decl[InfixOperatorFunction]/CurrModule: [' ']&&&& {#Boolish#}[#Boolish#];
// INFIX_AUTOCLOSURE_1-DAG: Decl[InfixOperatorFunction]/CurrModule: [' ']==== {#Boolish#}[#Boolish#];
// INFIX_AUTOCLOSURE_1-DAG: Decl[InfixOperatorFunction]/CurrModule: [' ']|||| {#Boolish#}[#Boolish#];
-// INFIX_AUTOCLOSURE_1: End completions
\ No newline at end of file
+// INFIX_AUTOCLOSURE_1: End completions
diff --git a/validation-test/IDE/slow_fixed/rdar45511835.swift b/validation-test/IDE/slow_fixed/rdar45511835.swift
new file mode 100644
index 0000000..4f9dd01
--- /dev/null
+++ b/validation-test/IDE/slow_fixed/rdar45511835.swift
@@ -0,0 +1,10 @@
+// RUN: %target-swift-ide-test -code-completion -code-completion-token=COMPLETE -source-filename=%s | %FileCheck %s
+
+// This used to take ~6 min to complete.
+
+func testing() {
+ return (["a"] + [1].map { String($0) })
+ .map { $0 + "b" as String }
+ .filter { $0 != "" } #^COMPLETE^#
+}
+// CHECK: Decl[InfixOperatorFunction]/{{.*}}: [' ']+ {#[String]#}[#[String]#]; name=+ [String]