diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index f607b4562a0cd..2b5d994e2038f 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -906,6 +906,9 @@ namespace swift { /// is for testing purposes. std::vector DebugForbidTypecheckPrefixes; + /// Disable the shrink phase of the expression type checker. + bool SolverDisableShrink = false; + /// Enable experimental operator designated types feature. bool EnableOperatorDesignatedTypes = false; diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 7a005271a3be1..5beee7143a796 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -832,6 +832,10 @@ def solver_scope_threshold_EQ : Joined<["-"], "solver-scope-threshold=">, def solver_trail_threshold_EQ : Joined<["-"], "solver-trail-threshold=">, HelpText<"Expression type checking trail change limit">; +def solver_disable_shrink : + Flag<["-"], "solver-disable-shrink">, + HelpText<"Disable the shrink phase of expression type checking">; + def solver_disable_splitter : Flag<["-"], "solver-disable-splitter">, HelpText<"Disable the component splitter phase of expression type checking">; diff --git a/include/swift/Sema/Constraint.h b/include/swift/Sema/Constraint.h index 41e6aebe4e52a..382a213c54d1a 100644 --- a/include/swift/Sema/Constraint.h +++ b/include/swift/Sema/Constraint.h @@ -818,6 +818,11 @@ class Constraint final : public llvm::ilist_node, }); } + /// Returns the number of resolved argument types for an applied disjunction + /// constraint. This is always zero for disjunctions that do not represent + /// an applied overload. + unsigned countResolvedArgumentTypes(ConstraintSystem &cs) const; + /// Determine if this constraint represents explicit conversion, /// e.g. coercion constraint "as X" which forms a disjunction. bool isExplicitConversion() const; diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index e6c93009e6d82..1147dfa6bd717 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -488,14 +488,6 @@ class TypeVariableType::Implementation { /// literal (represented by `ArrayExpr` and `DictionaryExpr` in AST). bool isCollectionLiteralType() const; - /// Determine whether this type variable represents a literal such - /// as an integer value, a floating-point value with and without a sign. - bool isNumberLiteralType() const; - - /// Determine whether this type variable represents a result type of a - /// function call. - bool isFunctionResult() const; - /// Retrieve the representative of the equivalence class to which this /// type variable belongs. /// @@ -2250,6 +2242,10 @@ class ConstraintSystem { llvm::SetVector TypeVariables; + /// Maps expressions to types for choosing a favored overload + /// type in a disjunction constraint. + llvm::DenseMap FavoredTypes; + /// Maps discovered closures to their types inferred /// from declared parameters/result and body. /// @@ -2462,6 +2458,74 @@ class ConstraintSystem { SynthesizedConformances; private: + /// Describe the candidate expression for partial solving. + /// This class used by shrink & solve methods which apply + /// variation of directional path consistency algorithm in attempt + /// to reduce scopes of the overload sets (disjunctions) in the system. + class Candidate { + Expr *E; + DeclContext *DC; + llvm::BumpPtrAllocator &Allocator; + + // Contextual Information. + Type CT; + ContextualTypePurpose CTP; + + public: + Candidate(ConstraintSystem &cs, Expr *expr, Type ct = Type(), + ContextualTypePurpose ctp = ContextualTypePurpose::CTP_Unused) + : E(expr), DC(cs.DC), Allocator(cs.Allocator), CT(ct), CTP(ctp) {} + + /// Return underlying expression. + Expr *getExpr() const { return E; } + + /// Try to solve this candidate sub-expression + /// and re-write it's OSR domains afterwards. + /// + /// \param shrunkExprs The set of expressions which + /// domains have been successfully shrunk so far. + /// + /// \returns true on solver failure, false otherwise. + bool solve(llvm::SmallSetVector &shrunkExprs); + + /// Apply solutions found by solver as reduced OSR sets for + /// for current and all of it's sub-expressions. + /// + /// \param solutions The solutions found by running solver on the + /// this candidate expression. + /// + /// \param shrunkExprs The set of expressions which + /// domains have been successfully shrunk so far. + void applySolutions( + llvm::SmallVectorImpl &solutions, + llvm::SmallSetVector &shrunkExprs) const; + + /// Check if attempt at solving of the candidate makes sense given + /// the current conditions - number of shrunk domains which is related + /// to the given candidate over the total number of disjunctions present. + static bool + isTooComplexGiven(ConstraintSystem *const cs, + llvm::SmallSetVector &shrunkExprs) { + SmallVector disjunctions; + cs->collectDisjunctions(disjunctions); + + unsigned unsolvedDisjunctions = disjunctions.size(); + for (auto *disjunction : disjunctions) { + auto *locator = disjunction->getLocator(); + if (!locator) + continue; + + if (auto *OSR = getAsExpr(locator->getAnchor())) { + if (shrunkExprs.count(OSR) > 0) + --unsolvedDisjunctions; + } + } + + // The threshold used to be `TypeCheckerOpts.SolverShrinkUnsolvedThreshold` + return unsolvedDisjunctions >= 10; + } + }; + /// Describes the current solver state. struct SolverState { SolverState(ConstraintSystem &cs, @@ -2985,6 +3049,15 @@ class ConstraintSystem { return nullptr; } + TypeBase* getFavoredType(Expr *E) { + assert(E != nullptr); + return this->FavoredTypes[E]; + } + void setFavoredType(Expr *E, TypeBase *T) { + assert(E != nullptr); + this->FavoredTypes[E] = T; + } + /// Set the type in our type map for the given node, and record the change /// in the trail. /// @@ -5239,11 +5312,19 @@ class ConstraintSystem { /// \returns true if an error occurred, false otherwise. bool solveSimplified(SmallVectorImpl &solutions); + /// Find reduced domains of disjunction constraints for given + /// expression, this is achieved to solving individual sub-expressions + /// and combining resolving types. Such algorithm is called directional + /// path consistency because it goes from children to parents for all + /// related sub-expressions taking union of their domains. + /// + /// \param expr The expression to find reductions for. + void shrink(Expr *expr); + /// Pick a disjunction from the InactiveConstraints list. /// - /// \returns The selected disjunction and a set of it's favored choices. - std::optional>> - selectDisjunction(); + /// \returns The selected disjunction. + Constraint *selectDisjunction(); /// Pick a conjunction from the InactiveConstraints list. /// @@ -5432,6 +5513,11 @@ class ConstraintSystem { bool applySolutionToBody(TapExpr *tapExpr, SyntacticElementTargetRewriter &rewriter); + /// Reorder the disjunctive clauses for a given expression to + /// increase the likelihood that a favored constraint will be successfully + /// resolved before any others. + void optimizeConstraints(Expr *e); + void startExpressionTimer(ExpressionTimer::AnchorType anchor); /// Determine if we've already explored too many paths in an @@ -6172,8 +6258,7 @@ class DisjunctionChoiceProducer : public BindingProducer { public: using Element = DisjunctionChoice; - DisjunctionChoiceProducer(ConstraintSystem &cs, Constraint *disjunction, - llvm::TinyPtrVector &favorites) + DisjunctionChoiceProducer(ConstraintSystem &cs, Constraint *disjunction) : BindingProducer(cs, disjunction->shouldRememberChoice() ? disjunction->getLocator() : nullptr), @@ -6183,11 +6268,6 @@ class DisjunctionChoiceProducer : public BindingProducer { assert(disjunction->getKind() == ConstraintKind::Disjunction); assert(!disjunction->shouldRememberChoice() || disjunction->getLocator()); - // Mark constraints as favored. This information - // is going to be used by partitioner. - for (auto *choice : favorites) - cs.favorConstraint(choice); - // Order and partition the disjunction choices. partitionDisjunction(Ordering, PartitionBeginning); } @@ -6232,9 +6312,8 @@ class DisjunctionChoiceProducer : public BindingProducer { // Partition the choices in the disjunction into groups that we will // iterate over in an order appropriate to attempt to stop before we // have to visit all of the options. - void - partitionDisjunction(SmallVectorImpl &Ordering, - SmallVectorImpl &PartitionBeginning); + void partitionDisjunction(SmallVectorImpl &Ordering, + SmallVectorImpl &PartitionBeginning); /// Partition the choices in the range \c first to \c last into groups and /// order the groups in the best order to attempt based on the argument diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 01fdeb8f42a14..6b81e36b2f7c6 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -1859,6 +1859,9 @@ static bool ParseTypeCheckerArgs(TypeCheckerOptions &Opts, ArgList &Args, Opts.DebugForbidTypecheckPrefixes.push_back(A); } + if (Args.getLastArg(OPT_solver_disable_shrink)) + Opts.SolverDisableShrink = true; + if (Args.getLastArg(OPT_solver_disable_splitter)) Opts.SolverDisableSplitter = true; diff --git a/lib/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index 1bf3c664b4bd9..e250812244310 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -13,7 +13,6 @@ add_swift_host_library(swiftSema STATIC CSStep.cpp CSTrail.cpp CSFix.cpp - CSOptimizer.cpp CSDiagnostics.cpp CodeSynthesis.cpp CodeSynthesisDistributedActor.cpp diff --git a/lib/Sema/CSBindings.cpp b/lib/Sema/CSBindings.cpp index 94cec862b8bad..f8d6759e304fe 100644 --- a/lib/Sema/CSBindings.cpp +++ b/lib/Sema/CSBindings.cpp @@ -31,6 +31,7 @@ using namespace swift; using namespace constraints; using namespace inference; + void ConstraintGraphNode::initBindingSet() { ASSERT(!hasBindingSet()); ASSERT(forRepresentativeVar()); @@ -38,12 +39,6 @@ void ConstraintGraphNode::initBindingSet() { Set.emplace(CG.getConstraintSystem(), TypeVar, Potential); } -/// Check whether there exists a type that could be implicitly converted -/// to a given type i.e. is the given type is Double or Optional<..> this -/// function is going to return true because CGFloat could be converted -/// to a Double and non-optional value could be injected into an optional. -static bool hasConversions(Type); - static std::optional checkTypeOfBinding(TypeVariableType *typeVar, Type type); @@ -1310,31 +1305,7 @@ bool BindingSet::isViable(PotentialBinding &binding, bool isTransitive) { if (!existingNTD || NTD != existingNTD) continue; - // What is going on in this method needs to be thoroughly re-evaluated! - // - // This logic aims to skip dropping bindings if - // collection type has conversions i.e. in situations like: - // - // [$T1] conv $T2 - // $T2 conv [(Int, String)] - // $T2.Element equal $T5.Element - // - // `$T1` could be bound to `(i: Int, v: String)` after - // `$T2` is bound to `[(Int, String)]` which is is a problem - // because it means that `$T2` was attempted to early - // before the solver had a chance to discover all viable - // bindings. - // - // Let's say existing binding is `[(Int, String)]` and - // relation is "exact", in this case there is no point - // tracking `[$T1]` because upcasts are only allowed for - // subtype and other conversions. - if (existing->Kind != AllowedBindingKind::Exact) { - if (existingType->isKnownStdlibCollectionType() && - hasConversions(existingType)) { - continue; - } - } + // FIXME: What is going on here needs to be thoroughly re-evaluated. // If new type has a type variable it shouldn't // be considered viable. diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index b94d96491b54c..d97fefd5e8e01 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -43,6 +43,10 @@ using namespace swift; using namespace swift::constraints; +static bool isArithmeticOperatorDecl(ValueDecl *vd) { + return vd && vd->getBaseIdentifier().isArithmeticOperator(); +} + static bool mergeRepresentativeEquivalenceClasses(ConstraintSystem &CS, TypeVariableType* tyvar1, TypeVariableType* tyvar2) { @@ -73,6 +77,698 @@ static bool mergeRepresentativeEquivalenceClasses(ConstraintSystem &CS, } namespace { + + /// Internal struct for tracking information about types within a series + /// of "linked" expressions. (Such as a chain of binary operator invocations.) + struct LinkedTypeInfo { + bool hasLiteral = false; + + llvm::SmallSet collectedTypes; + llvm::SmallVector binaryExprs; + }; + + /// Walks an expression sub-tree, and collects information about expressions + /// whose types are mutually dependent upon one another. + class LinkedExprCollector : public ASTWalker { + + llvm::SmallVectorImpl &LinkedExprs; + + public: + LinkedExprCollector(llvm::SmallVectorImpl &linkedExprs) + : LinkedExprs(linkedExprs) {} + + MacroWalking getMacroWalkingBehavior() const override { + return MacroWalking::Arguments; + } + + PreWalkResult walkToExprPre(Expr *expr) override { + if (isa(expr)) + return Action::SkipNode(expr); + + // Store top-level binary exprs for further analysis. + if (isa(expr) || + + // Literal exprs are contextually typed, so store them off as well. + isa(expr) || + + // We'd like to look at the elements of arrays and dictionaries. + isa(expr) || + isa(expr) || + + // assignment expression can involve anonymous closure parameters + // as source and destination, so it's beneficial for diagnostics if + // we look at the assignment. + isa(expr)) { + LinkedExprs.push_back(expr); + return Action::SkipNode(expr); + } + + return Action::Continue(expr); + } + + /// Ignore statements. + PreWalkResult walkToStmtPre(Stmt *stmt) override { + return Action::SkipNode(stmt); + } + + /// Ignore declarations. + PreWalkAction walkToDeclPre(Decl *decl) override { + return Action::SkipNode(); + } + + /// Ignore patterns. + PreWalkResult walkToPatternPre(Pattern *pat) override { + return Action::SkipNode(pat); + } + + /// Ignore types. + PreWalkAction walkToTypeReprPre(TypeRepr *T) override { + return Action::SkipNode(); + } + }; + + /// Given a collection of "linked" expressions, analyzes them for + /// commonalities regarding their types. This will help us compute a + /// "best common type" from the expression types. + class LinkedExprAnalyzer : public ASTWalker { + + LinkedTypeInfo <I; + ConstraintSystem &CS; + + public: + + LinkedExprAnalyzer(LinkedTypeInfo <i, ConstraintSystem &cs) : + LTI(lti), CS(cs) {} + + MacroWalking getMacroWalkingBehavior() const override { + return MacroWalking::Arguments; + } + + PreWalkResult walkToExprPre(Expr *expr) override { + if (isa(expr)) { + LTI.hasLiteral = true; + return Action::SkipNode(expr); + } + + if (isa(expr)) { + return Action::Continue(expr); + } + + if (auto UDE = dyn_cast(expr)) { + + if (CS.hasType(UDE)) + LTI.collectedTypes.insert(CS.getType(UDE).getPointer()); + + // Don't recurse into the base expression. + return Action::SkipNode(expr); + } + + + if (isa(expr)) { + return Action::SkipNode(expr); + } + + if (auto FVE = dyn_cast(expr)) { + LTI.collectedTypes.insert(CS.getType(FVE).getPointer()); + return Action::SkipNode(expr); + } + + if (auto DRE = dyn_cast(expr)) { + if (auto varDecl = dyn_cast(DRE->getDecl())) { + if (CS.hasType(DRE)) { + LTI.collectedTypes.insert(CS.getType(DRE).getPointer()); + } + return Action::SkipNode(expr); + } + } + + // In the case of a function application, we would have already captured + // the return type during constraint generation, so there's no use in + // looking any further. + if (isa(expr) && + !(isa(expr) || isa(expr) || + isa(expr))) { + return Action::SkipNode(expr); + } + + if (auto *binaryExpr = dyn_cast(expr)) { + LTI.binaryExprs.push_back(binaryExpr); + } + + if (auto favoredType = CS.getFavoredType(expr)) { + LTI.collectedTypes.insert(favoredType); + + return Action::SkipNode(expr); + } + + // Optimize branches of a conditional expression separately. + if (auto IE = dyn_cast(expr)) { + CS.optimizeConstraints(IE->getCondExpr()); + CS.optimizeConstraints(IE->getThenExpr()); + CS.optimizeConstraints(IE->getElseExpr()); + return Action::SkipNode(expr); + } + + // For exprs of a tuple, avoid favoring. (We need to allow for cases like + // (Int, Int32).) + if (isa(expr)) { + return Action::SkipNode(expr); + } + + // Coercion exprs have a rigid type, so there's no use in gathering info + // about them. + if (auto *coercion = dyn_cast(expr)) { + // Let's not collect information about types initialized by + // coercions just like we don't for regular initializer calls, + // because that might lead to overly eager type variable merging. + if (!coercion->isLiteralInit()) + LTI.collectedTypes.insert(CS.getType(expr).getPointer()); + return Action::SkipNode(expr); + } + + // Don't walk into subscript expressions - to do so would risk factoring + // the index expression into edge contraction. (We don't want to do this + // if the index expression is a literal type that differs from the return + // type of the subscript operation.) + if (isa(expr) || isa(expr)) { + return Action::SkipNode(expr); + } + + // Don't walk into unresolved member expressions - we avoid merging type + // variables inside UnresolvedMemberExpr and those outside, since they + // should be allowed to behave independently in CS. + if (isa(expr)) { + return Action::SkipNode(expr); + } + + return Action::Continue(expr); + } + + /// Ignore statements. + PreWalkResult walkToStmtPre(Stmt *stmt) override { + return Action::SkipNode(stmt); + } + + /// Ignore declarations. + PreWalkAction walkToDeclPre(Decl *decl) override { + return Action::SkipNode(); + } + + /// Ignore patterns. + PreWalkResult walkToPatternPre(Pattern *pat) override { + return Action::SkipNode(pat); + } + + /// Ignore types. + PreWalkAction walkToTypeReprPre(TypeRepr *T) override { + return Action::SkipNode(); + } + }; + + /// For a given expression, given information that is global to the + /// expression, attempt to derive a favored type for it. + void computeFavoredTypeForExpr(Expr *expr, ConstraintSystem &CS) { + LinkedTypeInfo lti; + + expr->walk(LinkedExprAnalyzer(lti, CS)); + + // Check whether we can proceed with favoring. + if (llvm::any_of(lti.binaryExprs, [](const BinaryExpr *op) { + auto *ODRE = dyn_cast(op->getFn()); + if (!ODRE) + return false; + + // Attempting to favor based on operand types is wrong for + // nil-coalescing operator. + auto identifier = ODRE->getDecls().front()->getBaseIdentifier(); + return identifier.isNilCoalescingOperator(); + })) { + return; + } + + if (lti.collectedTypes.size() == 1) { + // TODO: Compute the BCT. + + // It's only useful to favor the type instead of + // binding it directly to arguments/result types, + // which means in case it has been miscalculated + // solver can still make progress. + auto favoredTy = (*lti.collectedTypes.begin())->getWithoutSpecifierType(); + CS.setFavoredType(expr, favoredTy.getPointer()); + + // If we have a chain of identical binop expressions with homogeneous + // argument types, we can directly simplify the associated constraint + // graph. + auto simplifyBinOpExprTyVars = [&]() { + // Don't attempt to do linking if there are + // literals intermingled with other inferred types. + if (lti.hasLiteral) + return; + + for (auto binExp1 : lti.binaryExprs) { + for (auto binExp2 : lti.binaryExprs) { + if (binExp1 == binExp2) + continue; + + auto fnTy1 = CS.getType(binExp1)->getAs(); + auto fnTy2 = CS.getType(binExp2)->getAs(); + + if (!(fnTy1 && fnTy2)) + return; + + auto ODR1 = dyn_cast(binExp1->getFn()); + auto ODR2 = dyn_cast(binExp2->getFn()); + + if (!(ODR1 && ODR2)) + return; + + // TODO: We currently limit this optimization to known arithmetic + // operators, but we should be able to broaden this out to + // logical operators as well. + if (!isArithmeticOperatorDecl(ODR1->getDecls()[0])) + return; + + if (ODR1->getDecls()[0]->getBaseName() != + ODR2->getDecls()[0]->getBaseName()) + return; + + // All things equal, we can merge the tyvars for the function + // types. + auto rep1 = CS.getRepresentative(fnTy1); + auto rep2 = CS.getRepresentative(fnTy2); + + if (rep1 != rep2) { + CS.mergeEquivalenceClasses(rep1, rep2, + /*updateWorkList*/ false); + } + + auto odTy1 = CS.getType(ODR1)->getAs(); + auto odTy2 = CS.getType(ODR2)->getAs(); + + if (odTy1 && odTy2) { + auto odRep1 = CS.getRepresentative(odTy1); + auto odRep2 = CS.getRepresentative(odTy2); + + // Since we'll be choosing the same overload, we can merge + // the overload tyvar as well. + if (odRep1 != odRep2) + CS.mergeEquivalenceClasses(odRep1, odRep2, + /*updateWorkList*/ false); + } + } + } + }; + + simplifyBinOpExprTyVars(); + } + } + + /// Determine whether the given parameter type and argument should be + /// "favored" because they match exactly. + bool isFavoredParamAndArg(ConstraintSystem &CS, Type paramTy, Type argTy, + Type otherArgTy = Type()) { + // Determine the argument type. + argTy = argTy->getWithoutSpecifierType(); + + // Do the types match exactly? + if (paramTy->isEqual(argTy)) + return true; + + // Don't favor narrowing conversions. + if (argTy->isDouble() && paramTy->isCGFloat()) + return false; + + llvm::SmallSetVector literalProtos; + if (auto argTypeVar = argTy->getAs()) { + auto constraints = CS.getConstraintGraph().gatherConstraints( + argTypeVar, ConstraintGraph::GatheringKind::EquivalenceClass, + [](Constraint *constraint) { + return constraint->getKind() == ConstraintKind::LiteralConformsTo; + }); + + for (auto constraint : constraints) { + literalProtos.insert(constraint->getProtocol()); + } + } + + // Dig out the second argument type. + if (otherArgTy) + otherArgTy = otherArgTy->getWithoutSpecifierType(); + + for (auto literalProto : literalProtos) { + // If there is another, concrete argument, check whether it's type + // conforms to the literal protocol and test against it directly. + // This helps to avoid 'widening' the favored type to the default type for + // the literal. + if (otherArgTy && otherArgTy->getAnyNominal()) { + if (otherArgTy->isEqual(paramTy) && + CS.lookupConformance(otherArgTy, literalProto)) { + return true; + } + } else if (Type defaultType = + TypeChecker::getDefaultType(literalProto, CS.DC)) { + // If there is a default type for the literal protocol, check whether + // it is the same as the parameter type. + // Check whether there is a default type to compare against. + if (paramTy->isEqual(defaultType) || + (defaultType->isDouble() && paramTy->isCGFloat())) + return true; + } + } + + return false; + } + + /// Favor certain overloads in a call based on some basic analysis + /// of the overload set and call arguments. + /// + /// \param expr The application. + /// \param isFavored Determine whether the given overload is favored, passing + /// it the "effective" overload type when it's being called. + /// \param mustConsider If provided, a function to detect the presence of + /// overloads which inhibit any overload from being favored. + void favorCallOverloads(ApplyExpr *expr, + ConstraintSystem &CS, + llvm::function_ref isFavored, + std::function + mustConsider = nullptr) { + // Find the type variable associated with the function, if any. + auto tyvarType = CS.getType(expr->getFn())->getAs(); + if (!tyvarType || CS.getFixedType(tyvarType)) + return; + + // This type variable is only currently associated with the function + // being applied, and the only constraint attached to it should + // be the disjunction constraint for the overload group. + auto disjunction = CS.getUnboundBindOverloadDisjunction(tyvarType); + if (!disjunction) + return; + + // Find the favored constraints and mark them. + SmallVector newlyFavoredConstraints; + unsigned numFavoredConstraints = 0; + Constraint *firstFavored = nullptr; + for (auto constraint : disjunction->getNestedConstraints()) { + auto *decl = constraint->getOverloadChoice().getDeclOrNull(); + if (!decl) + continue; + + if (mustConsider && mustConsider(decl)) { + // Roll back any constraints we favored. + for (auto favored : newlyFavoredConstraints) + favored->setFavored(false); + + return; + } + + Type overloadType = CS.getEffectiveOverloadType( + constraint->getLocator(), constraint->getOverloadChoice(), + /*allowMembers=*/true, CS.DC); + if (!overloadType) + continue; + + if (!CS.isDeclUnavailable(decl, constraint->getLocator()) && + !decl->getAttrs().hasAttribute() && + isFavored(decl, overloadType)) { + // If we might need to roll back the favored constraints, keep + // track of those we are favoring. + if (mustConsider && !constraint->isFavored()) + newlyFavoredConstraints.push_back(constraint); + + constraint->setFavored(); + ++numFavoredConstraints; + if (!firstFavored) + firstFavored = constraint; + } + } + + // If there was one favored constraint, set the favored type based on its + // result type. + if (numFavoredConstraints == 1) { + auto overloadChoice = firstFavored->getOverloadChoice(); + auto overloadType = CS.getEffectiveOverloadType( + firstFavored->getLocator(), overloadChoice, /*allowMembers=*/true, + CS.DC); + auto resultType = overloadType->castTo()->getResult(); + if (!resultType->hasTypeParameter()) + CS.setFavoredType(expr, resultType.getPointer()); + } + } + + /// Return a pair, containing the total parameter count of a function, coupled + /// with the number of non-default parameters. + std::pair getParamCount(ValueDecl *VD) { + auto fTy = VD->getInterfaceType()->castTo(); + + size_t nOperands = fTy->getParams().size(); + size_t nNoDefault = 0; + + if (auto AFD = dyn_cast(VD)) { + assert(!AFD->hasImplicitSelfDecl()); + for (auto param : *AFD->getParameters()) { + if (!param->isDefaultArgument()) + ++nNoDefault; + } + } else { + nNoDefault = nOperands; + } + + return { nOperands, nNoDefault }; + } + + bool hasContextuallyFavorableResultType(AnyFunctionType *choice, + Type contextualTy) { + // No restrictions of what result could be. + if (!contextualTy) + return true; + + auto resultTy = choice->getResult(); + // Result type of the call matches expected contextual type. + return contextualTy->isEqual(resultTy); + } + + /// Favor unary operator constraints where we have exact matches + /// for the operand and contextual type. + void favorMatchingUnaryOperators(ApplyExpr *expr, + ConstraintSystem &CS) { + auto *unaryArg = expr->getArgs()->getUnaryExpr(); + assert(unaryArg); + + // Determine whether the given declaration is favored. + auto isFavoredDecl = [&](ValueDecl *value, Type type) -> bool { + auto fnTy = type->getAs(); + if (!fnTy) + return false; + + auto params = fnTy->getParams(); + if (params.size() != 1) + return false; + + auto paramTy = params[0].getPlainType(); + auto argTy = CS.getType(unaryArg); + + // There are no CGFloat overloads on some of the unary operators, so + // in order to preserve current behavior, let's not favor overloads + // which would result in conversion from CGFloat to Double; otherwise + // it would lead to ambiguities. + if (argTy->isCGFloat() && paramTy->isDouble()) + return false; + + return isFavoredParamAndArg(CS, paramTy, argTy) && + hasContextuallyFavorableResultType( + fnTy, + CS.getContextualType(expr, /*forConstraint=*/false)); + }; + + favorCallOverloads(expr, CS, isFavoredDecl); + } + + void favorMatchingOverloadExprs(ApplyExpr *expr, + ConstraintSystem &CS) { + // Find the argument type. + size_t nArgs = expr->getArgs()->size(); + auto fnExpr = expr->getFn(); + + auto mustConsiderVariadicGenericOverloads = [&](ValueDecl *overload) { + if (overload->getAttrs().hasAttribute()) + return false; + + auto genericContext = overload->getAsGenericContext(); + if (!genericContext) + return false; + + auto *GPL = genericContext->getGenericParams(); + if (!GPL) + return false; + + return llvm::any_of(GPL->getParams(), + [&](const GenericTypeParamDecl *GP) { + return GP->isParameterPack(); + }); + }; + + // Check to ensure that we have an OverloadedDeclRef, and that we're not + // favoring multiple overload constraints. (Otherwise, in this case + // favoring is useless. + if (auto ODR = dyn_cast(fnExpr)) { + bool haveMultipleApplicableOverloads = false; + + for (auto VD : ODR->getDecls()) { + if (VD->getInterfaceType()->is()) { + auto nParams = getParamCount(VD); + + if (nArgs == nParams.first) { + if (haveMultipleApplicableOverloads) { + return; + } else { + haveMultipleApplicableOverloads = true; + } + } + } + } + + // Determine whether the given declaration is favored. + auto isFavoredDecl = [&](ValueDecl *value, Type type) -> bool { + // We want to consider all options for calls that might contain the code + // completion location, as missing arguments after the completion + // location are valid (since it might be that they just haven't been + // written yet). + if (CS.isForCodeCompletion()) + return false; + + if (!type->is()) + return false; + + auto paramCount = getParamCount(value); + + return nArgs == paramCount.first || + nArgs == paramCount.second; + }; + + favorCallOverloads(expr, CS, isFavoredDecl, + mustConsiderVariadicGenericOverloads); + } + + // We only currently perform favoring for unary args. + auto *unaryArg = expr->getArgs()->getUnlabeledUnaryExpr(); + if (!unaryArg) + return; + + if (auto favoredTy = CS.getFavoredType(unaryArg)) { + // Determine whether the given declaration is favored. + auto isFavoredDecl = [&](ValueDecl *value, Type type) -> bool { + auto fnTy = type->getAs(); + if (!fnTy || fnTy->getParams().size() != 1) + return false; + + return favoredTy->isEqual(fnTy->getParams()[0].getPlainType()); + }; + + // This is a hack to ensure we always consider the protocol requirement + // itself when calling something that has a default implementation in an + // extension. Otherwise, the extension method might be favored if we're + // inside an extension context, since any archetypes in the parameter + // list could match exactly. + auto mustConsider = [&](ValueDecl *value) -> bool { + return isa(value->getDeclContext()) || + mustConsiderVariadicGenericOverloads(value); + }; + + favorCallOverloads(expr, CS, isFavoredDecl, mustConsider); + } + } + + /// Favor binary operator constraints where we have exact matches + /// for the operands and contextual type. + void favorMatchingBinaryOperators(ApplyExpr *expr, ConstraintSystem &CS) { + // If we're generating constraints for a binary operator application, + // there are two special situations to consider: + // 1. If the type checker has any newly created functions with the + // operator's name. If it does, the overloads were created after the + // associated overloaded id expression was created, and we'll need to + // add a new disjunction constraint for the new set of overloads. + // 2. If any component argument expressions (nested or otherwise) are + // literals, we can favor operator overloads whose argument types are + // identical to the literal type, or whose return types are identical + // to any contextual type associated with the application expression. + + // Find the argument types. + auto *args = expr->getArgs(); + auto *lhs = args->getExpr(0); + auto *rhs = args->getExpr(1); + + auto firstArgTy = CS.getType(lhs); + auto secondArgTy = CS.getType(rhs); + + auto isOptionalWithMatchingObjectType = [](Type optional, + Type object) -> bool { + if (auto objTy = optional->getRValueType()->getOptionalObjectType()) + return objTy->getRValueType()->isEqual(object->getRValueType()); + + return false; + }; + + auto isPotentialForcingOpportunity = [&](Type first, Type second) -> bool { + return isOptionalWithMatchingObjectType(first, second) || + isOptionalWithMatchingObjectType(second, first); + }; + + // Determine whether the given declaration is favored. + auto isFavoredDecl = [&](ValueDecl *value, Type type) -> bool { + auto fnTy = type->getAs(); + if (!fnTy) + return false; + + auto firstFavoredTy = CS.getFavoredType(lhs); + auto secondFavoredTy = CS.getFavoredType(rhs); + + auto favoredExprTy = CS.getFavoredType(expr); + + if (isArithmeticOperatorDecl(value)) { + // If the parent has been favored on the way down, propagate that + // information to its children. + // TODO: This is only valid for arithmetic expressions. + if (!firstFavoredTy) { + CS.setFavoredType(lhs, favoredExprTy); + firstFavoredTy = favoredExprTy; + } + + if (!secondFavoredTy) { + CS.setFavoredType(rhs, favoredExprTy); + secondFavoredTy = favoredExprTy; + } + } + + auto params = fnTy->getParams(); + if (params.size() != 2) + return false; + + auto firstParamTy = params[0].getOldType(); + auto secondParamTy = params[1].getOldType(); + + auto contextualTy = CS.getContextualType(expr, /*forConstraint=*/false); + + // Avoid favoring overloads that would require narrowing conversion + // to match the arguments. + { + if (firstArgTy->isDouble() && firstParamTy->isCGFloat()) + return false; + + if (secondArgTy->isDouble() && secondParamTy->isCGFloat()) + return false; + } + + return (isFavoredParamAndArg(CS, firstParamTy, firstArgTy, secondArgTy) || + isFavoredParamAndArg(CS, secondParamTy, secondArgTy, + firstArgTy)) && + firstParamTy->isEqual(secondParamTy) && + !isPotentialForcingOpportunity(firstArgTy, secondArgTy) && + hasContextuallyFavorableResultType(fnTy, contextualTy); + }; + + favorCallOverloads(expr, CS, isFavoredDecl); + } + /// If \p expr is a call and that call contains the code completion token, /// add the expressions of all arguments after the code completion token to /// \p ignoredArguments. @@ -103,6 +799,62 @@ namespace { } } } + + class ConstraintOptimizer : public ASTWalker { + ConstraintSystem &CS; + + public: + + ConstraintOptimizer(ConstraintSystem &cs) : + CS(cs) {} + + MacroWalking getMacroWalkingBehavior() const override { + return MacroWalking::Arguments; + } + + PreWalkResult walkToExprPre(Expr *expr) override { + if (CS.isArgumentIgnoredForCodeCompletion(expr)) { + return Action::SkipNode(expr); + } + + if (auto applyExpr = dyn_cast(expr)) { + if (isa(applyExpr) || + isa(applyExpr)) { + favorMatchingUnaryOperators(applyExpr, CS); + } else if (isa(applyExpr)) { + favorMatchingBinaryOperators(applyExpr, CS); + } else { + favorMatchingOverloadExprs(applyExpr, CS); + } + } + + // If the paren expr has a favored type, and the subExpr doesn't, + // propagate downwards. Otherwise, propagate upwards. + if (auto parenExpr = dyn_cast(expr)) { + if (!CS.getFavoredType(parenExpr->getSubExpr())) { + CS.setFavoredType(parenExpr->getSubExpr(), + CS.getFavoredType(parenExpr)); + } else if (!CS.getFavoredType(parenExpr)) { + CS.setFavoredType(parenExpr, + CS.getFavoredType(parenExpr->getSubExpr())); + } + } + + if (isa(expr)) + return Action::SkipNode(expr); + + return Action::Continue(expr); + } + /// Ignore statements. + PreWalkResult walkToStmtPre(Stmt *stmt) override { + return Action::SkipNode(stmt); + } + + /// Ignore declarations. + PreWalkAction walkToDeclPre(Decl *decl) override { + return Action::SkipNode(); + } + }; } // end anonymous namespace void TypeVarRefCollector::inferTypeVars(Decl *D) { @@ -348,6 +1100,18 @@ namespace { if (isLValueBase) outputTy = LValueType::get(outputTy); } + } else if (auto dictTy = CS.isDictionaryType(baseObjTy)) { + auto keyTy = dictTy->first; + auto valueTy = dictTy->second; + + if (argList->isUnlabeledUnary()) { + auto argTy = CS.getType(argList->getExpr(0)); + if (isFavoredParamAndArg(CS, keyTy, argTy)) { + outputTy = OptionalType::get(valueTy); + if (isLValueBase) + outputTy = LValueType::get(outputTy); + } + } } } @@ -403,6 +1167,7 @@ namespace { Type fixedOutputType = CS.getFixedTypeRecursive(outputTy, /*wantRValue=*/false); if (!fixedOutputType->isTypeVariableOrMember()) { + CS.setFavoredType(anchor, fixedOutputType.getPointer()); outputTy = fixedOutputType; } @@ -803,6 +1568,11 @@ namespace { getParentPackExpansionExpr(E)); } } + + if (!knownType->hasPlaceholder()) { + // Set the favored type for this expression to the known type. + CS.setFavoredType(E, knownType.getPointer()); + } } } @@ -1281,6 +2051,9 @@ namespace { CS.getASTContext()); } + if (auto favoredTy = CS.getFavoredType(expr->getSubExpr())) { + CS.setFavoredType(expr, favoredTy); + } return CS.getType(expr->getSubExpr()); } @@ -2555,6 +3328,7 @@ namespace { Type fixedType = CS.getFixedTypeRecursive(resultType, /*wantRvalue=*/true); if (!fixedType->isTypeVariableOrMember()) { + CS.setFavoredType(expr, fixedType.getPointer()); resultType = fixedType; } @@ -3594,7 +4368,12 @@ static Expr *generateConstraintsFor(ConstraintSystem &cs, Expr *expr, ConstraintGenerator cg(cs, DC); ConstraintWalker cw(cg); - return expr->walk(cw); + Expr *result = expr->walk(cw); + + if (result) + cs.optimizeConstraints(result); + + return result; } bool ConstraintSystem::generateWrappedPropertyTypeConstraints( @@ -4338,6 +5117,26 @@ ConstraintSystem::applyPropertyWrapperToParameter( return getTypeMatchSuccess(); } +void ConstraintSystem::optimizeConstraints(Expr *e) { + if (getASTContext().TypeCheckerOpts.DisableConstraintSolverPerformanceHacks) + return; + + SmallVector linkedExprs; + + // Collect any linked expressions. + LinkedExprCollector collector(linkedExprs); + e->walk(collector); + + // Favor types, as appropriate. + for (auto linkedExpr : linkedExprs) { + computeFavoredTypeForExpr(linkedExpr, *this); + } + + // Optimize the constraints. + ConstraintOptimizer optimizer(*this); + e->walk(optimizer); +} + struct ResolvedMemberResult::Implementation { llvm::SmallVector AllDecls; unsigned ViableStartIdx; diff --git a/lib/Sema/CSOptimizer.cpp b/lib/Sema/CSOptimizer.cpp deleted file mode 100644 index a93ee36bf7423..0000000000000 --- a/lib/Sema/CSOptimizer.cpp +++ /dev/null @@ -1,1324 +0,0 @@ -//===--- CSOptimizer.cpp - Constraint Optimizer ---------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// -// -// This file implements disjunction and other constraint optimizations. -// -//===----------------------------------------------------------------------===// - -#include "TypeChecker.h" -#include "swift/AST/ConformanceLookup.h" -#include "swift/AST/ExistentialLayout.h" -#include "swift/AST/GenericSignature.h" -#include "swift/Basic/OptionSet.h" -#include "swift/Sema/ConstraintGraph.h" -#include "swift/Sema/ConstraintSystem.h" -#include "llvm/ADT/BitVector.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/TinyPtrVector.h" -#include "llvm/Support/SaveAndRestore.h" -#include "llvm/Support/raw_ostream.h" -#include -#include - -using namespace swift; -using namespace constraints; - -namespace { - -struct DisjunctionInfo { - /// The score of the disjunction is the highest score from its choices. - /// If the score is nullopt it means that the disjunction is not optimizable. - std::optional Score; - /// The highest scoring choices that could be favored when disjunction - /// is attempted. - llvm::TinyPtrVector FavoredChoices; - - DisjunctionInfo() = default; - DisjunctionInfo(double score, ArrayRef favoredChoices = {}) - : Score(score), FavoredChoices(favoredChoices) {} -}; - -// TODO: both `isIntegerType` and `isFloatType` should be available on Type -// as `isStdlib{Integer, Float}Type`. - -static bool isIntegerType(Type type) { - return type->isInt() || type->isInt8() || type->isInt16() || - type->isInt32() || type->isInt64() || type->isUInt() || - type->isUInt8() || type->isUInt16() || type->isUInt32() || - type->isUInt64(); -} - -static bool isFloatType(Type type) { - return type->isFloat() || type->isDouble() || type->isFloat80(); -} - -static bool isUnboundArrayType(Type type) { - if (auto *UGT = type->getAs()) - return UGT->getDecl() == type->getASTContext().getArrayDecl(); - return false; -} - -static bool isSupportedOperator(Constraint *disjunction) { - if (!isOperatorDisjunction(disjunction)) - return false; - - auto choices = disjunction->getNestedConstraints(); - auto *decl = getOverloadChoiceDecl(choices.front()); - - auto name = decl->getBaseIdentifier(); - if (name.isArithmeticOperator() || name.isStandardComparisonOperator() || - name.isBitwiseOperator()) { - return true; - } - - // Operators like &<<, &>>, &+, .== etc. - if (llvm::any_of(choices, [](Constraint *choice) { - return isSIMDOperator(getOverloadChoiceDecl(choice)); - })) { - return true; - } - - return false; -} - -static bool isSupportedSpecialConstructor(ConstructorDecl *ctor) { - if (auto *selfDecl = ctor->getImplicitSelfDecl()) { - auto selfTy = selfDecl->getInterfaceType(); - /// Support `Int*`, `Float*` and `Double` initializers since their generic - /// overloads are not too complicated. - return selfTy && (isIntegerType(selfTy) || isFloatType(selfTy)); - } - return false; -} - -static bool isStandardComparisonOperator(ValueDecl *decl) { - return decl->isOperator() && - decl->getBaseIdentifier().isStandardComparisonOperator(); -} - -static bool isArithmeticOperator(ValueDecl *decl) { - return decl->isOperator() && decl->getBaseIdentifier().isArithmeticOperator(); -} - -static bool isSupportedDisjunction(Constraint *disjunction) { - auto choices = disjunction->getNestedConstraints(); - - if (isSupportedOperator(disjunction)) - return true; - - if (auto *ctor = dyn_cast_or_null( - getOverloadChoiceDecl(choices.front()))) { - if (isSupportedSpecialConstructor(ctor)) - return true; - } - - // Non-operator disjunctions are supported only if they don't - // have any generic choices. - return llvm::all_of(choices, [&](Constraint *choice) { - if (choice->isDisabled()) - return true; - - if (choice->getKind() != ConstraintKind::BindOverload) - return false; - - if (auto *decl = getOverloadChoiceDecl(choice)) { - // Cannot optimize declarations that return IUO because - // they form a disjunction over a result type once attempted. - if (decl->isImplicitlyUnwrappedOptional()) - return false; - - return decl->getInterfaceType()->is(); - } - - return false; - }); -} - -NullablePtr getApplicableFnConstraint(ConstraintGraph &CG, - Constraint *disjunction) { - auto *boundVar = disjunction->getNestedConstraints()[0] - ->getFirstType() - ->getAs(); - if (!boundVar) - return nullptr; - - auto constraints = CG.gatherConstraints( - boundVar, ConstraintGraph::GatheringKind::EquivalenceClass, - [](Constraint *constraint) { - return constraint->getKind() == ConstraintKind::ApplicableFunction; - }); - - if (constraints.size() != 1) - return nullptr; - - auto *applicableFn = constraints.front(); - // Unapplied disjunction could appear as a argument to applicable function, - // we are not interested in that. - return applicableFn->getSecondType()->isEqual(boundVar) ? applicableFn - : nullptr; -} - -void forEachDisjunctionChoice( - ConstraintSystem &cs, Constraint *disjunction, - llvm::function_ref - callback) { - for (auto constraint : disjunction->getNestedConstraints()) { - if (constraint->isDisabled()) - continue; - - if (constraint->getKind() != ConstraintKind::BindOverload) - continue; - - auto choice = constraint->getOverloadChoice(); - auto *decl = choice.getDeclOrNull(); - if (!decl) - continue; - - // Ignore declarations that come from implicitly imported modules - // when `MemberImportVisibility` feature is enabled otherwise - // we might end up favoring an overload that would be diagnosed - // as unavailable later. - if (cs.getASTContext().LangOpts.hasFeature( - Feature::MemberImportVisibility)) { - if (auto *useDC = constraint->getOverloadUseDC()) { - if (!useDC->isDeclImported(decl)) - continue; - } - } - - // If disjunction choice is unavailable or disfavored we cannot - // do anything with it. - if (decl->getAttrs().hasAttribute() || - cs.isDeclUnavailable(decl, disjunction->getLocator())) - continue; - - Type overloadType = - cs.getEffectiveOverloadType(disjunction->getLocator(), choice, - /*allowMembers=*/true, cs.DC); - - if (!overloadType || !overloadType->is()) - continue; - - callback(constraint, decl, overloadType->castTo()); - } -} - -static OverloadedDeclRefExpr *isOverloadedDeclRef(Constraint *disjunction) { - assert(disjunction->getKind() == ConstraintKind::Disjunction); - - auto *locator = disjunction->getLocator(); - if (locator->getPath().empty()) - return getAsExpr(locator->getAnchor()); - return nullptr; -} - -static unsigned numOverloadChoicesMatchingOnArity(OverloadedDeclRefExpr *ODRE, - ArgumentList *arguments) { - return llvm::count_if(ODRE->getDecls(), [&arguments](auto *choice) { - if (auto *paramList = getParameterList(choice)) - return arguments->size() == paramList->size(); - return false; - }); -} - -/// This maintains an "old hack" behavior where overloads of some -/// `OverloadedDeclRef` calls were favored purely based on number of -/// argument and (non-defaulted) parameters matching. -static void findFavoredChoicesBasedOnArity( - ConstraintSystem &cs, Constraint *disjunction, ArgumentList *argumentList, - llvm::function_ref favoredChoice) { - auto *ODRE = isOverloadedDeclRef(disjunction); - if (!ODRE) - return; - - if (numOverloadChoicesMatchingOnArity(ODRE, argumentList) > 1) - return; - - auto isVariadicGenericOverload = [&](ValueDecl *choice) { - auto genericContext = choice->getAsGenericContext(); - if (!genericContext) - return false; - - auto *GPL = genericContext->getGenericParams(); - if (!GPL) - return false; - - return llvm::any_of(GPL->getParams(), [&](const GenericTypeParamDecl *GP) { - return GP->isParameterPack(); - }); - }; - - bool hasVariadicGenerics = false; - SmallVector favored; - - forEachDisjunctionChoice( - cs, disjunction, - [&](Constraint *choice, ValueDecl *decl, FunctionType *overloadType) { - if (isVariadicGenericOverload(decl)) - hasVariadicGenerics = true; - - if (overloadType->getNumParams() == argumentList->size() || - llvm::count_if(*getParameterList(decl), [](auto *param) { - return !param->isDefaultArgument(); - }) == argumentList->size()) - favored.push_back(choice); - }); - - if (hasVariadicGenerics) - return; - - for (auto *choice : favored) - favoredChoice(choice); -} - -} // end anonymous namespace - -/// Given a set of disjunctions, attempt to determine -/// favored choices in the current context. -static void determineBestChoicesInContext( - ConstraintSystem &cs, SmallVectorImpl &disjunctions, - llvm::DenseMap &result) { - double bestOverallScore = 0.0; - - auto recordResult = [&bestOverallScore, &result](Constraint *disjunction, - DisjunctionInfo &&info) { - bestOverallScore = std::max(bestOverallScore, info.Score.value_or(0)); - result.try_emplace(disjunction, info); - }; - - for (auto *disjunction : disjunctions) { - // If this is a compiler synthesized disjunction, mark it as supported - // and record all of the previously favored choices. Such disjunctions - // include - explicit coercions, IUO references,injected implicit - // initializers for CGFloat<->Double conversions and restrictions with - // multiple choices. - if (disjunction->countFavoredNestedConstraints() > 0) { - DisjunctionInfo info(/*score=*/2.0); - llvm::copy_if(disjunction->getNestedConstraints(), - std::back_inserter(info.FavoredChoices), - [](Constraint *choice) { return choice->isFavored(); }); - recordResult(disjunction, std::move(info)); - continue; - } - - auto applicableFn = - getApplicableFnConstraint(cs.getConstraintGraph(), disjunction); - - if (applicableFn.isNull()) { - auto *locator = disjunction->getLocator(); - if (auto expr = getAsExpr(locator->getAnchor())) { - auto *parentExpr = cs.getParentExpr(expr); - // Look through optional evaluation, so - // we can cover expressions like `a?.b + 2`. - if (isExpr(parentExpr)) - parentExpr = cs.getParentExpr(parentExpr); - - if (parentExpr) { - // If this is a chained member reference or a direct operator - // argument it could be prioritized since it helps to establish - // context for other calls i.e. `(a.)b + 2` if `a` and/or `b` - // are disjunctions they should be preferred over `+`. - switch (parentExpr->getKind()) { - case ExprKind::Binary: - case ExprKind::PrefixUnary: - case ExprKind::PostfixUnary: - case ExprKind::UnresolvedDot: { - llvm::SmallVector favoredChoices; - // Favor choices that don't require application. - llvm::copy_if( - disjunction->getNestedConstraints(), - std::back_inserter(favoredChoices), [](Constraint *choice) { - auto *decl = getOverloadChoiceDecl(choice); - return decl && - !decl->getInterfaceType()->is(); - }); - recordResult(disjunction, {/*score=*/1.0, favoredChoices}); - continue; - } - - default: - break; - } - } - } - - continue; - } - - auto argFuncType = - applicableFn.get()->getFirstType()->getAs(); - - auto argumentList = cs.getArgumentList(applicableFn.get()->getLocator()); - if (!argumentList) - return; - - for (const auto &argument : *argumentList) { - if (auto *expr = argument.getExpr()) { - // Directly `<#...#>` or has one inside. - if (isa(expr) || - cs.containsIDEInspectionTarget(expr)) - return; - } - } - - // This maintains an "old hack" behavior where overloads - // of `OverloadedDeclRef` calls were favored purely - // based on arity of arguments and parameters matching. - { - llvm::TinyPtrVector favoredChoices; - findFavoredChoicesBasedOnArity(cs, disjunction, argumentList, - [&favoredChoices](Constraint *choice) { - favoredChoices.push_back(choice); - }); - - if (!favoredChoices.empty()) { - recordResult(disjunction, {/*score=*/0.01, favoredChoices}); - continue; - } - } - - if (!isSupportedDisjunction(disjunction)) - continue; - - SmallVector argsWithLabels; - { - argsWithLabels.append(argFuncType->getParams().begin(), - argFuncType->getParams().end()); - FunctionType::relabelParams(argsWithLabels, argumentList); - } - - struct ArgumentCandidate { - Type type; - // The candidate type is derived from a literal expression. - bool fromLiteral : 1; - // The candidate type is derived from a call to an - // initializer i.e. `Double(...)`. - bool fromInitializerCall : 1; - - ArgumentCandidate(Type type, bool fromLiteral = false, - bool fromInitializerCall = false) - : type(type), fromLiteral(fromLiteral), - fromInitializerCall(fromInitializerCall) {} - }; - - SmallVector, 2> - argumentCandidates; - argumentCandidates.resize(argFuncType->getNumParams()); - - llvm::TinyPtrVector resultTypes; - - for (unsigned i = 0, n = argFuncType->getNumParams(); i != n; ++i) { - const auto ¶m = argFuncType->getParams()[i]; - auto argType = cs.simplifyType(param.getPlainType()); - - SmallVector types; - if (auto *typeVar = argType->getAs()) { - auto bindingSet = cs.getBindingsFor(typeVar); - - for (const auto &binding : bindingSet.Bindings) { - types.push_back({binding.BindingType}); - } - - for (const auto &literal : bindingSet.Literals) { - if (literal.second.hasDefaultType()) { - // Add primary default type - types.push_back( - {literal.second.getDefaultType(), /*fromLiteral=*/true}); - } - } - - // Helps situations like `1 + {Double, CGFloat}(...)` by inferring - // a type for the second operand of `+` based on a type being constructed. - // - // Currently limited to Double and CGFloat only since we need to - // support implicit `Double<->CGFloat` conversion. - if (typeVar->getImpl().isFunctionResult() && - isOperatorDisjunction(disjunction)) { - auto resultLoc = typeVar->getImpl().getLocator(); - if (auto *call = getAsExpr(resultLoc->getAnchor())) { - if (auto *typeExpr = dyn_cast(call->getFn())) { - auto instanceTy = cs.getType(typeExpr)->getMetatypeInstanceType(); - if (instanceTy->isDouble() || instanceTy->isCGFloat()) - types.push_back({instanceTy, /*fromLiteral=*/false, - /*fromInitializerCall=*/true}); - } - } - } - } else { - types.push_back({argType, /*fromLiteral=*/false}); - } - - argumentCandidates[i].append(types); - } - - auto resultType = cs.simplifyType(argFuncType->getResult()); - if (auto *typeVar = resultType->getAs()) { - auto bindingSet = cs.getBindingsFor(typeVar); - - for (const auto &binding : bindingSet.Bindings) { - resultTypes.push_back(binding.BindingType); - } - } else { - resultTypes.push_back(resultType); - } - - // Determine whether all of the argument candidates are inferred from literals. - // This information is going to be used later on when we need to decide how to - // score a matching choice. - bool onlyLiteralCandidates = - argFuncType->getNumParams() > 0 && - llvm::none_of( - indices(argFuncType->getParams()), [&](const unsigned argIdx) { - auto &candidates = argumentCandidates[argIdx]; - return llvm::any_of(candidates, [&](const auto &candidate) { - return !candidate.fromLiteral; - }); - }); - - // Match arguments to the given overload choice. - auto matchArguments = [&](OverloadChoice choice, FunctionType *overloadType) - -> std::optional { - auto *decl = choice.getDeclOrNull(); - assert(decl); - - auto hasAppliedSelf = - decl->hasCurriedSelf() && - doesMemberRefApplyCurriedSelf(choice.getBaseType(), decl); - - ParameterListInfo paramListInfo(overloadType->getParams(), decl, - hasAppliedSelf); - - MatchCallArgumentListener listener; - return matchCallArguments(argsWithLabels, overloadType->getParams(), - paramListInfo, - argumentList->getFirstTrailingClosureIndex(), - /*allow fixes*/ false, listener, std::nullopt); - }; - - // Determine whether the candidate type is a subclass of the superclass - // type. - std::function isSubclassOf = [&](Type candidateType, - Type superclassType) { - // Conversion from a concrete type to its existential value. - if (superclassType->isExistentialType() && !superclassType->isAny()) { - auto layout = superclassType->getExistentialLayout(); - - if (auto layoutConstraint = layout.getLayoutConstraint()) { - if (layoutConstraint->isClass() && - !(candidateType->isClassExistentialType() || - candidateType->mayHaveSuperclass())) - return false; - } - - if (layout.explicitSuperclass && - !isSubclassOf(candidateType, layout.explicitSuperclass)) - return false; - - return llvm::all_of(layout.getProtocols(), [&](ProtocolDecl *P) { - if (auto superclass = P->getSuperclassDecl()) { - if (!isSubclassOf(candidateType, - superclass->getDeclaredInterfaceType())) - return false; - } - - return bool(TypeChecker::containsProtocol(candidateType, P, - /*allowMissing=*/false)); - }); - } - - auto *subclassDecl = candidateType->getClassOrBoundGenericClass(); - auto *superclassDecl = superclassType->getClassOrBoundGenericClass(); - - if (!(subclassDecl && superclassDecl)) - return false; - - return superclassDecl->isSuperclassOf(subclassDecl); - }; - - enum class MatchFlag { - OnParam = 0x01, - Literal = 0x02, - ExactOnly = 0x04, - DisableCGFloatDoubleConversion = 0x08, - }; - - using MatchOptions = OptionSet; - - // Perform a limited set of checks to determine whether the candidate - // could possibly match the parameter type: - // - // - Equality - // - Protocol conformance(s) - // - Optional injection - // - Superclass conversion - // - Array-to-pointer conversion - // - Value to existential conversion - // - Exact match on top-level types - // - // In situations when it's not possible to determine whether a candidate - // type matches a parameter type (i.e. when partially resolved generic - // types are matched) this function is going to produce \c std::nullopt - // instead of `0` that indicates "not a match". - std::function(GenericSignature, ValueDecl *, Type, - Type, MatchOptions)> - scoreCandidateMatch = - [&](GenericSignature genericSig, ValueDecl *choice, - Type candidateType, Type paramType, - MatchOptions options) -> std::optional { - auto areEqual = [&](Type a, Type b) { - return a->getDesugaredType()->isEqual(b->getDesugaredType()); - }; - - auto isCGFloatDoubleConversionSupported = [&options]() { - // CGFloat <-> Double conversion is supposed only while - // match argument candidates to parameters. - return options.contains(MatchFlag::OnParam) && - !options.contains(MatchFlag::DisableCGFloatDoubleConversion); - }; - - // Allow CGFloat -> Double widening conversions between - // candidate argument types and parameter types. This would - // make sure that Double is always preferred over CGFloat - // when using literals and ranking supported disjunction - // choices. Narrowing conversion (Double -> CGFloat) should - // be delayed as much as possible. - if (isCGFloatDoubleConversionSupported()) { - if (candidateType->isCGFloat() && paramType->isDouble()) { - return options.contains(MatchFlag::Literal) ? 0.2 : 0.9; - } - } - - // Match `[...]` to Array<...> and/or `ExpressibleByArrayLiteral` - // conforming types. - if (options.contains(MatchFlag::OnParam) && - options.contains(MatchFlag::Literal) && - isUnboundArrayType(candidateType)) { - // If an exact match is requested favor only `[...]` to `Array<...>` - // since everything else is going to increase to score. - if (options.contains(MatchFlag::ExactOnly)) - return paramType->isArrayType() ? 1 : 0; - - // Otherwise, check if the other side conforms to - // `ExpressibleByArrayLiteral` protocol (in some way). - // We want an overly optimistic result here to avoid - // under-favoring. - auto &ctx = cs.getASTContext(); - return checkConformanceWithoutContext( - paramType, - ctx.getProtocol( - KnownProtocolKind::ExpressibleByArrayLiteral), - /*allowMissing=*/true) - ? 0.3 - : 0; - } - - if (options.contains(MatchFlag::ExactOnly)) - return areEqual(candidateType, paramType) ? 1 : 0; - - // Exact match between candidate and parameter types. - if (areEqual(candidateType, paramType)) { - return options.contains(MatchFlag::Literal) ? 0.3 : 1; - } - - if (options.contains(MatchFlag::Literal)) { - // Integer and floating-point literals can match any parameter - // type that conforms to `ExpressibleBy{Integer, Float}Literal` - // protocol but since that would constitute a non-default binding - // the score has to be slightly lowered. - if (!paramType->hasTypeParameter()) { - if (candidateType->isInt() && - TypeChecker::conformsToKnownProtocol( - paramType, KnownProtocolKind::ExpressibleByIntegerLiteral)) - return paramType->isDouble() ? 0.2 : 0.3; - - if (candidateType->isDouble() && - TypeChecker::conformsToKnownProtocol( - paramType, KnownProtocolKind::ExpressibleByFloatLiteral)) - return 0.3; - } - - return 0; - } - - // Check whether match would require optional injection. - { - SmallVector candidateOptionals; - SmallVector paramOptionals; - - candidateType = - candidateType->lookThroughAllOptionalTypes(candidateOptionals); - paramType = paramType->lookThroughAllOptionalTypes(paramOptionals); - - if (!candidateOptionals.empty() || !paramOptionals.empty()) { - if (paramOptionals.size() >= candidateOptionals.size()) { - auto score = scoreCandidateMatch(genericSig, choice, candidateType, - paramType, options); - // Injection lowers the score slightly to comply with - // old behavior where exact matches on operator parameter - // types were always preferred. - return score == 1 && choice->isOperator() ? 0.9 : score; - } - - // Optionality mismatch. - return 0; - } - } - - // Candidate could be converted to a superclass. - if (isSubclassOf(candidateType, paramType)) - return 1; - - // Possible Array -> Unsafe*Pointer conversion. - if (options.contains(MatchFlag::OnParam)) { - if (candidateType->isArrayType() && - paramType->getAnyPointerElementType()) - return 1; - } - - // If both argument and parameter are tuples of the same arity, - // it's a match. - { - if (auto *candidateTuple = candidateType->getAs()) { - auto *paramTuple = paramType->getAs(); - if (paramTuple && - candidateTuple->getNumElements() == paramTuple->getNumElements()) - return 1; - } - } - - // Check protocol requirement(s) if this parameter is a - // generic parameter type. - if (genericSig && paramType->isTypeParameter()) { - // Light-weight check if cases where `checkRequirements` is not - // applicable. - auto checkProtocolRequirementsOnly = [&]() -> double { - auto protocolRequirements = - genericSig->getRequiredProtocols(paramType); - if (llvm::all_of(protocolRequirements, [&](ProtocolDecl *protocol) { - return bool(cs.lookupConformance(candidateType, protocol)); - })) { - if (auto *GP = paramType->getAs()) { - auto *paramDecl = GP->getDecl(); - if (paramDecl && paramDecl->isOpaqueType()) - return 1.0; - } - return 0.7; - } - - return 0; - }; - - // If candidate is not fully resolved or is matched against a - // dependent member type (i.e. `Self.T`), let's check conformances - // only and lower the score. - if (candidateType->hasTypeVariable() || - paramType->is()) { - return checkProtocolRequirementsOnly(); - } - - // Cannot match anything but generic type parameters here. - if (!paramType->is()) - return std::nullopt; - - bool hasUnsatisfiableRequirements = false; - SmallVector requirements; - - for (const auto &requirement : genericSig.getRequirements()) { - if (hasUnsatisfiableRequirements) - break; - - llvm::SmallPtrSet toExamine; - - auto recordReferencesGenericParams = [&toExamine](Type type) { - type.visit([&toExamine](Type innerTy) { - if (auto *GP = innerTy->getAs()) - toExamine.insert(GP); - }); - }; - - recordReferencesGenericParams(requirement.getFirstType()); - - if (requirement.getKind() != RequirementKind::Layout) - recordReferencesGenericParams(requirement.getSecondType()); - - if (llvm::any_of(toExamine, [&](GenericTypeParamType *GP) { - return paramType->isEqual(GP); - })) { - requirements.push_back(requirement); - // If requirement mentions other generic parameters - // `checkRequirements` would because we don't have - // candidate substitutions for anything but the current - // parameter type. - hasUnsatisfiableRequirements |= toExamine.size() > 1; - } - } - - // If some of the requirements cannot be satisfied, because - // they reference other generic parameters, for example: - // ``, let's perform a - // light-weight check instead of skipping this overload choice. - if (hasUnsatisfiableRequirements) - return checkProtocolRequirementsOnly(); - - // If the candidate type is fully resolved, let's check all of - // the requirements that are associated with the corresponding - // parameter, if all of them are satisfied this candidate is - // an exact match. - auto result = checkRequirements( - requirements, - [¶mType, &candidateType](SubstitutableType *type) -> Type { - if (type->isEqual(paramType)) - return candidateType; - return ErrorType::get(type); - }, - SubstOptions(std::nullopt)); - - // Concrete operator overloads are always more preferable to - // generic ones if there are exact or subtype matches, for - // everything else the solver should try both concrete and - // generic and disambiguate during ranking. - if (result == CheckRequirementsResult::Success) - return choice->isOperator() ? 0.9 : 1.0; - - return 0; - } - - // Parameter is generic, let's check whether top-level - // types match i.e. Array as a parameter. - // - // This is slightly better than all of the conformances matching - // because the parameter is concrete and could split the graph. - if (paramType->hasTypeParameter()) { - auto *candidateDecl = candidateType->getAnyNominal(); - auto *paramDecl = paramType->getAnyNominal(); - - if (candidateDecl && paramDecl && candidateDecl == paramDecl) - return 0.8; - } - - return 0; - }; - - // The choice with the best score. - double bestScore = 0.0; - SmallVector, 2> favoredChoices; - - // Preserves old behavior where, for unary calls, the solver - // would not consider choices that didn't match on the number - // of parameters (regardless of defaults) and only exact - // matches were favored. - bool preserveFavoringOfUnlabeledUnaryArgument = false; - if (argumentList->isUnlabeledUnary()) { - auto ODRE = isOverloadedDeclRef(disjunction); - preserveFavoringOfUnlabeledUnaryArgument = - !ODRE || numOverloadChoicesMatchingOnArity(ODRE, argumentList) < 2; - } - - forEachDisjunctionChoice( - cs, disjunction, - [&](Constraint *choice, ValueDecl *decl, FunctionType *overloadType) { - GenericSignature genericSig; - { - if (auto *GF = dyn_cast(decl)) { - genericSig = GF->getGenericSignature(); - } else if (auto *SD = dyn_cast(decl)) { - genericSig = SD->getGenericSignature(); - } - } - - auto matchings = - matchArguments(choice->getOverloadChoice(), overloadType); - if (!matchings) - return; - - // If all of the arguments are literals, let's prioritize exact - // matches to filter out non-default literal bindings which otherwise - // could cause "over-favoring". - bool favorExactMatchesOnly = onlyLiteralCandidates; - - if (preserveFavoringOfUnlabeledUnaryArgument) { - // Old behavior completely disregarded the fact that some of - // the parameters could be defaulted. - if (overloadType->getNumParams() != 1) - return; - - favorExactMatchesOnly = true; - } - - // This is important for SIMD operators in particular because - // a lot of their overloads have same-type requires to a concrete - // type: `(_: SIMD*, ...) -> ...`. - if (genericSig) { - overloadType = overloadType->getReducedType(genericSig) - ->castTo(); - } - - double score = 0.0; - unsigned numDefaulted = 0; - for (unsigned paramIdx = 0, n = overloadType->getNumParams(); - paramIdx != n; ++paramIdx) { - const auto ¶m = overloadType->getParams()[paramIdx]; - - auto argIndices = matchings->parameterBindings[paramIdx]; - switch (argIndices.size()) { - case 0: - // Current parameter is defaulted, mark and continue. - ++numDefaulted; - continue; - - case 1: - // One-to-one match between argument and parameter. - break; - - default: - // Cannot deal with multiple possible matchings at the moment. - return; - } - - auto argIdx = argIndices.front(); - - // Looks like there is nothing know about the argument. - if (argumentCandidates[argIdx].empty()) - continue; - - const auto paramFlags = param.getParameterFlags(); - - // If parameter is variadic we cannot compare because we don't know - // real arity. - if (paramFlags.isVariadic()) - continue; - - auto paramType = param.getPlainType(); - - // FIXME: Let's skip matching function types for now - // because they have special rules for e.g. Concurrency - // (around @Sendable) and @convention(c). - if (paramType->is()) - continue; - - // The idea here is to match the parameter type against - // all of the argument candidate types and pick the best - // match (i.e. exact equality one). - // - // If none of the candidates match exactly and they are - // all bound concrete types, we consider this is mismatch - // at this parameter position and remove the overload choice - // from consideration. - double bestCandidateScore = 0; - llvm::BitVector mismatches(argumentCandidates[argIdx].size()); - - for (unsigned candidateIdx : - indices(argumentCandidates[argIdx])) { - // If one of the candidates matched exactly there is no reason - // to continue checking. - if (bestCandidateScore == 1) - break; - - auto candidate = argumentCandidates[argIdx][candidateIdx]; - - // `inout` parameter accepts only l-value argument. - if (paramFlags.isInOut() && !candidate.type->is()) { - mismatches.set(candidateIdx); - continue; - } - - MatchOptions options(MatchFlag::OnParam); - if (candidate.fromLiteral) - options |= MatchFlag::Literal; - if (favorExactMatchesOnly) - options |= MatchFlag::ExactOnly; - - // Disable CGFloat -> Double conversion for unary operators. - // - // Some of the unary operators, i.e. prefix `-`, don't have - // CGFloat variants and expect generic `FloatingPoint` overload - // to match CGFloat type. Let's not attempt `CGFloat` -> `Double` - // conversion for unary operators because it always leads - // to a worse solutions vs. generic overloads. - if (n == 1 && decl->isOperator()) - options |= MatchFlag::DisableCGFloatDoubleConversion; - - // Disable implicit CGFloat -> Double widening conversion if - // argument is an explicit call to `CGFloat` initializer. - if (candidate.type->isCGFloat() && - candidate.fromInitializerCall) - options |= MatchFlag::DisableCGFloatDoubleConversion; - - // The specifier for a candidate only matters for `inout` check. - auto candidateScore = scoreCandidateMatch( - genericSig, decl, candidate.type->getWithoutSpecifierType(), - paramType, options); - - if (!candidateScore) - continue; - - if (candidateScore > 0) { - bestCandidateScore = - std::max(bestCandidateScore, candidateScore.value()); - continue; - } - - // Only established arguments could be considered mismatches, - // literal default types should be regarded as holes if they - // didn't match. - if (!candidate.fromLiteral && !candidate.type->hasTypeVariable()) - mismatches.set(candidateIdx); - } - - // If none of the candidates for this parameter matched, let's - // drop this overload from any further consideration. - if (mismatches.all()) - return; - - score += bestCandidateScore; - } - - // An overload whether all of the parameters are defaulted - // that's called without arguments. - if (numDefaulted == overloadType->getNumParams()) - return; - - // Average the score to avoid disfavoring disjunctions with fewer - // parameters. - score /= (overloadType->getNumParams() - numDefaulted); - - // Make sure that the score is uniform for all disjunction - // choices that match on literals only, this would make sure that - // in operator chains that consist purely of literals we'd - // always prefer outermost disjunction instead of innermost - // one. - // - // Preferring outer disjunction first works better in situations - // when contextual type for the whole chain becomes available at - // some point during solving at it would allow for faster pruning. - if (score > 0 && onlyLiteralCandidates && decl->isOperator()) - score = 0.1; - - // If one of the result types matches exactly, that's a good - // indication that overload choice should be favored. - // - // If nothing is known about the arguments it's only safe to - // check result for operators (except to standard comparison - // ones that all have the same result type), regular - // functions/methods and especially initializers could end up - // with a lot of favored overloads because on the result type alone. - if (decl->isOperator() && !isStandardComparisonOperator(decl)) { - if (llvm::any_of(resultTypes, [&](const Type candidateResultTy) { - // Avoid increasing weight based on CGFloat result type - // match because that could require narrowing conversion - // in the arguments and that is always detrimental. - // - // For example, `has_CGFloat_param(1.0 + 2.0)` should use - // `+(_: Double, _: Double) -> Double` instead of - // `+(_: CGFloat, _: CGFloat) -> CGFloat` which would match - // parameter of `has_CGFloat_param` exactly but use a - // narrowing conversion for both literals. - if (candidateResultTy->lookThroughAllOptionalTypes() - ->isCGFloat()) - return false; - - return scoreCandidateMatch(genericSig, decl, - overloadType->getResult(), - candidateResultTy, - /*options=*/{}) > 0; - })) { - score += 1.0; - } - } - - if (score > 0) { - // Nudge the score slightly to prefer concrete homogeneous - // arithmetic operators. - // - // This is an opportunistic optimization based on the operator - // use patterns where homogeneous operators are the most - // heavily used ones. - if (isArithmeticOperator(decl) && - overloadType->getNumParams() == 2) { - auto resultTy = overloadType->getResult(); - if (!resultTy->hasTypeParameter() && - llvm::all_of(overloadType->getParams(), - [&resultTy](const auto ¶m) { - return param.getPlainType()->isEqual(resultTy); - })) - score += 0.1; - } - - favoredChoices.push_back({choice, score}); - bestScore = std::max(bestScore, score); - } - }); - - if (cs.isDebugMode()) { - PrintOptions PO; - PO.PrintTypesForDebugging = true; - - llvm::errs().indent(cs.solverState->getCurrentIndent()) - << "<<< Disjunction " - << disjunction->getNestedConstraints()[0]->getFirstType()->getString( - PO) - << " with score " << bestScore << "\n"; - } - - bestOverallScore = std::max(bestOverallScore, bestScore); - - DisjunctionInfo info(/*score=*/bestScore); - - for (const auto &choice : favoredChoices) { - if (choice.second == bestScore) - info.FavoredChoices.push_back(choice.first); - } - - recordResult(disjunction, std::move(info)); - } - - if (cs.isDebugMode() && bestOverallScore > 0) { - PrintOptions PO; - PO.PrintTypesForDebugging = true; - - auto getLogger = [&](unsigned extraIndent = 0) -> llvm::raw_ostream & { - return llvm::errs().indent(cs.solverState->getCurrentIndent() + - extraIndent); - }; - - { - auto &log = getLogger(); - log << "(Optimizing disjunctions: ["; - - interleave( - disjunctions, - [&](const auto *disjunction) { - log << disjunction->getNestedConstraints()[0] - ->getFirstType() - ->getString(PO); - }, - [&]() { log << ", "; }); - - log << "]\n"; - } - - getLogger(/*extraIndent=*/4) - << "Best overall score = " << bestOverallScore << '\n'; - - for (auto *disjunction : disjunctions) { - auto &entry = result[disjunction]; - getLogger(/*extraIndent=*/4) - << "[Disjunction '" - << disjunction->getNestedConstraints()[0]->getFirstType()->getString( - PO) - << "' with score = " << entry.Score.value_or(0) << '\n'; - - for (const auto *choice : entry.FavoredChoices) { - auto &log = getLogger(/*extraIndent=*/6); - - log << "- "; - choice->print(log, &cs.getASTContext().SourceMgr); - log << '\n'; - } - - getLogger(/*extraIdent=*/4) << "]\n"; - } - - getLogger() << ")\n"; - } -} - -// Attempt to find a disjunction of bind constraints where all options -// in the disjunction are binding the same type variable. -// -// Prefer disjunctions where the bound type variable is also the -// right-hand side of a conversion constraint, since having a concrete -// type that we're converting to can make it possible to split the -// constraint system into multiple ones. -static Constraint * -selectBestBindingDisjunction(ConstraintSystem &cs, - SmallVectorImpl &disjunctions) { - - if (disjunctions.empty()) - return nullptr; - - auto getAsTypeVar = [&cs](Type type) { - return cs.simplifyType(type)->getRValueType()->getAs(); - }; - - Constraint *firstBindDisjunction = nullptr; - for (auto *disjunction : disjunctions) { - auto choices = disjunction->getNestedConstraints(); - assert(!choices.empty()); - - auto *choice = choices.front(); - if (choice->getKind() != ConstraintKind::Bind) - continue; - - // We can judge disjunction based on the single choice - // because all of choices (of bind overload set) should - // have the same left-hand side. - // Only do this for simple type variable bindings, not for - // bindings like: ($T1) -> $T2 bind String -> Int - auto *typeVar = getAsTypeVar(choice->getFirstType()); - if (!typeVar) - continue; - - if (!firstBindDisjunction) - firstBindDisjunction = disjunction; - - auto constraints = cs.getConstraintGraph().gatherConstraints( - typeVar, ConstraintGraph::GatheringKind::EquivalenceClass, - [](Constraint *constraint) { - return constraint->getKind() == ConstraintKind::Conversion; - }); - - for (auto *constraint : constraints) { - if (typeVar == getAsTypeVar(constraint->getSecondType())) - return disjunction; - } - } - - // If we had any binding disjunctions, return the first of - // those. These ensure that we attempt to bind types earlier than - // trying the elements of other disjunctions, which can often mean - // we fail faster. - return firstBindDisjunction; -} - -/// Prioritize `build{Block, Expression, ...}` and any chained -/// members that are connected to individual builder elements -/// i.e. `ForEach(...) { ... }.padding(...)`, once `ForEach` -/// is resolved, `padding` should be prioritized because its -/// requirements can help prune the solution space before the -/// body is checked. -static Constraint * -selectDisjunctionInResultBuilderContext(ConstraintSystem &cs, - ArrayRef disjunctions) { - auto context = AnyFunctionRef::fromDeclContext(cs.DC); - if (!context) - return nullptr; - - if (!cs.getAppliedResultBuilderTransform(context.value())) - return nullptr; - - std::pair best{nullptr, 0}; - for (auto *disjunction : disjunctions) { - auto *member = - getAsExpr(disjunction->getLocator()->getAnchor()); - if (!member) - continue; - - // Attempt `build{Block, Expression, ...} first because they - // provide contextual information for the inner calls. - if (isResultBuilderMethodReference(cs.getASTContext(), member)) - return disjunction; - - Expr *curr = member; - bool disqualified = false; - // Walk up the parent expression chain and check whether this - // disjunction represents one of the members in a chain that - // leads up to `buildExpression` (if defined by the builder) - // or to a pattern binding for `$__builderN` (the walk won't - // find any argument position locations in that case). - while (auto parent = cs.getParentExpr(curr)) { - if (!(isExpr(parent) || isExpr(parent))) { - disqualified = true; - break; - } - - if (auto *call = getAsExpr(parent)) { - // The current parent appears in an argument position. - if (call->getFn() != curr) { - // Allow expressions that appear in a argument position to - // `build{Expression, Block, ...} methods. - if (auto *UDE = getAsExpr(call->getFn())) { - disqualified = - !isResultBuilderMethodReference(cs.getASTContext(), UDE); - } else { - disqualified = true; - } - } - } - - if (disqualified) - break; - - curr = parent; - } - - if (disqualified) - continue; - - if (auto depth = cs.getExprDepth(member)) { - if (!best.first || best.second > depth) - best = std::make_pair(disjunction, depth.value()); - } - } - - return best.first; -} - -std::optional>> -ConstraintSystem::selectDisjunction() { - SmallVector disjunctions; - - collectDisjunctions(disjunctions); - if (disjunctions.empty()) - return std::nullopt; - - if (auto *disjunction = selectBestBindingDisjunction(*this, disjunctions)) - return std::make_pair(disjunction, llvm::TinyPtrVector()); - - llvm::DenseMap favorings; - determineBestChoicesInContext(*this, disjunctions, favorings); - - if (auto *disjunction = - selectDisjunctionInResultBuilderContext(*this, disjunctions)) { - return std::make_pair(disjunction, favorings[disjunction].FavoredChoices); - } - - // Pick the disjunction with the smallest number of favored, then active - // choices. - auto bestDisjunction = std::min_element( - disjunctions.begin(), disjunctions.end(), - [&](Constraint *first, Constraint *second) -> bool { - unsigned firstActive = first->countActiveNestedConstraints(); - unsigned secondActive = second->countActiveNestedConstraints(); - - auto &[firstScore, firstFavoredChoices] = favorings[first]; - auto &[secondScore, secondFavoredChoices] = favorings[second]; - - // Rank based on scores only if both disjunctions are supported. - if (firstScore && secondScore) { - // If both disjunctions have the same score they should be ranked - // based on number of favored/active choices. - if (*firstScore != *secondScore) - return *firstScore > *secondScore; - } - - unsigned numFirstFavored = firstFavoredChoices.size(); - unsigned numSecondFavored = secondFavoredChoices.size(); - - if (numFirstFavored == numSecondFavored) { - if (firstActive != secondActive) - return firstActive < secondActive; - } - - numFirstFavored = numFirstFavored ? numFirstFavored : firstActive; - numSecondFavored = numSecondFavored ? numSecondFavored : secondActive; - - return numFirstFavored < numSecondFavored; - }); - - if (bestDisjunction != disjunctions.end()) - return std::make_pair(*bestDisjunction, - favorings[*bestDisjunction].FavoredChoices); - - return std::nullopt; -} diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index ff7da127505bc..1a8fd13908134 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -9937,6 +9937,7 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName, // If this is true, we're using type construction syntax (Foo()) rather // than an explicit call to `init` (Foo.init()). bool isImplicitInit = false; + TypeBase *favoredType = nullptr; if (memberName.isSimpleName(DeclBaseName::createConstructor())) { SmallVector parts; if (auto anchor = memberLocator->getAnchor()) { @@ -9944,6 +9945,17 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName, if (!path.empty()) if (path.back().getKind() == ConstraintLocator::ConstructorMember) isImplicitInit = true; + + if (auto *applyExpr = getAsExpr(anchor)) { + if (auto *argExpr = applyExpr->getArgs()->getUnlabeledUnaryExpr()) { + favoredType = getFavoredType(argExpr); + + if (!favoredType) { + optimizeConstraints(argExpr); + favoredType = getFavoredType(argExpr); + } + } + } } } @@ -10052,6 +10064,30 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName, hasInstanceMethods = true; } + // If the invocation's argument expression has a favored type, + // use that information to determine whether a specific overload for + // the candidate should be favored. + if (isa(decl) && favoredType && + result.FavoredChoice == ~0U) { + auto *ctor = cast(decl); + + // Only try and favor monomorphic unary initializers. + if (!ctor->isGenericContext()) { + if (!ctor->getMethodInterfaceType()->hasError()) { + // The constructor might have an error type because we don't skip + // invalid decls for code completion + auto args = ctor->getMethodInterfaceType() + ->castTo() + ->getParams(); + if (args.size() == 1 && !args[0].hasLabel() && + args[0].getPlainType()->isEqual(favoredType)) { + if (!isDeclUnavailable(decl, memberLocator)) + result.FavoredChoice = result.ViableCandidates.size(); + } + } + } + } + const auto isUnsupportedExistentialMemberAccess = [&] { // We may not be able to derive a well defined type for an existential // member access if the member's signature references 'Self'. @@ -14841,19 +14877,9 @@ ConstraintSystem::simplifyRestrictedConstraintImpl( restriction == ConversionRestrictionKind::CGFloatToDouble ? 2 : 10; if (restriction == ConversionRestrictionKind::DoubleToCGFloat) { - SmallVector originalPath; - auto anchor = locator.getLocatorParts(originalPath); - - SourceRange range; - ArrayRef path(originalPath); - simplifyLocator(anchor, path, range); - - if (path.empty() || llvm::all_of(path, [](const LocatorPathElt &elt) { - return elt.is(); - })) { - if (auto *expr = getAsExpr(anchor)) - if (auto depth = getExprDepth(expr)) - impact = (*depth + 1) * impact; + if (auto *anchor = locator.trySimplifyToExpr()) { + if (auto depth = getExprDepth(anchor)) + impact = (*depth + 1) * impact; } } else if (locator.directlyAt() || locator.endsWith()) { diff --git a/lib/Sema/CSSolver.cpp b/lib/Sema/CSSolver.cpp index 1e9d4e9bb4a25..7df858000490b 100644 --- a/lib/Sema/CSSolver.cpp +++ b/lib/Sema/CSSolver.cpp @@ -787,6 +787,556 @@ ConstraintSystem::solveSingle(FreeTypeVariableBinding allowFreeTypeVariables, return std::move(solutions[0]); } +bool ConstraintSystem::Candidate::solve( + llvm::SmallSetVector &shrunkExprs) { + // Don't attempt to solve candidate if there is closure + // expression involved, because it's handled specially + // by parent constraint system (e.g. parameter lists). + bool containsClosure = false; + E->forEachChildExpr([&](Expr *childExpr) -> Expr * { + if (isa(childExpr)) { + containsClosure = true; + return nullptr; + } + return childExpr; + }); + + if (containsClosure) + return false; + + auto cleanupImplicitExprs = [&](Expr *expr) { + expr->forEachChildExpr([&](Expr *childExpr) -> Expr * { + Type type = childExpr->getType(); + if (childExpr->isImplicit() && type && type->hasTypeVariable()) + childExpr->setType(Type()); + return childExpr; + }); + }; + + // Allocate new constraint system for sub-expression. + ConstraintSystem cs(DC, std::nullopt); + + // Set up expression type checker timer for the candidate. + cs.startExpressionTimer(E); + + // Generate constraints for the new system. + if (auto generatedExpr = cs.generateConstraints(E, DC)) { + E = generatedExpr; + } else { + // Failure to generate constraint system for sub-expression + // means we can't continue solving sub-expressions. + cleanupImplicitExprs(E); + return true; + } + + // If this candidate is too complex given the number + // of the domains we have reduced so far, let's bail out early. + if (isTooComplexGiven(&cs, shrunkExprs)) + return false; + + auto &ctx = cs.getASTContext(); + if (cs.isDebugMode()) { + auto &log = llvm::errs(); + auto indent = cs.solverState ? cs.solverState->getCurrentIndent() : 0; + log.indent(indent) << "--- Solving candidate for shrinking at "; + auto R = E->getSourceRange(); + if (R.isValid()) { + R.print(log, ctx.SourceMgr, /*PrintText=*/ false); + } else { + log << ""; + } + log << " ---\n"; + + E->dump(log, indent); + log << '\n'; + cs.print(log); + } + + // If there is contextual type present, add an explicit "conversion" + // constraint to the system. + if (!CT.isNull()) { + auto constraintKind = ConstraintKind::Conversion; + if (CTP == CTP_CallArgument) + constraintKind = ConstraintKind::ArgumentConversion; + + cs.addConstraint(constraintKind, cs.getType(E), CT, + cs.getConstraintLocator(E), /*isFavored=*/true); + } + + // Try to solve the system and record all available solutions. + llvm::SmallVector solutions; + { + SolverState state(cs, FreeTypeVariableBinding::Allow); + + // Use solve which doesn't try to filter solution list. + // Because we want the whole set of possible domain choices. + cs.solveImpl(solutions); + } + + if (cs.isDebugMode()) { + auto &log = llvm::errs(); + auto indent = cs.solverState ? cs.solverState->getCurrentIndent() : 0; + if (solutions.empty()) { + log << "\n"; + log.indent(indent) << "--- No Solutions ---\n"; + } else { + log << "\n"; + log.indent(indent) << "--- Solutions ---\n"; + for (unsigned i = 0, n = solutions.size(); i != n; ++i) { + auto &solution = solutions[i]; + log << "\n"; + log.indent(indent) << "--- Solution #" << i << " ---\n"; + solution.dump(log, indent); + } + } + } + + // Record found solutions as suggestions. + this->applySolutions(solutions, shrunkExprs); + + // Let's double-check if we have any implicit expressions + // with type variables and nullify their types. + cleanupImplicitExprs(E); + + // No solutions for the sub-expression means that either main expression + // needs salvaging or it's inconsistent (read: doesn't have solutions). + return solutions.empty(); +} + +void ConstraintSystem::Candidate::applySolutions( + llvm::SmallVectorImpl &solutions, + llvm::SmallSetVector &shrunkExprs) const { + // A collection of OSRs with their newly reduced domains, + // it's domains are sets because multiple solutions can have the same + // choice for one of the type variables, and we want no duplication. + llvm::SmallDenseMap> + domains; + for (auto &solution : solutions) { + auto &score = solution.getFixedScore(); + + // Avoid any solutions with implicit value conversions + // because they might get reverted later when more context + // becomes available. + if (score.Data[SK_ImplicitValueConversion] > 0) + continue; + + for (auto choice : solution.overloadChoices) { + // Some of the choices might not have locators. + if (!choice.getFirst()) + continue; + + auto anchor = choice.getFirst()->getAnchor(); + auto *OSR = getAsExpr(anchor); + // Anchor is not available or expression is not an overload set. + if (!OSR) + continue; + + auto overload = choice.getSecond().choice; + auto type = overload.getDecl()->getInterfaceType(); + + // One of the solutions has polymorphic type associated with one of its + // type variables. Such functions can only be properly resolved + // via complete expression, so we'll have to forget solutions + // we have already recorded. They might not include all viable overload + // choices. + if (type->is()) { + return; + } + + domains[OSR].insert(overload.getDecl()); + } + } + + // Reduce the domains. + for (auto &domain : domains) { + auto OSR = domain.getFirst(); + auto &choices = domain.getSecond(); + + // If the domain wasn't reduced, skip it. + if (OSR->getDecls().size() == choices.size()) continue; + + // Update the expression with the reduced domain. + MutableArrayRef decls( + Allocator.Allocate(choices.size()), + choices.size()); + + std::uninitialized_copy(choices.begin(), choices.end(), decls.begin()); + OSR->setDecls(decls); + + // Record successfully shrunk expression. + shrunkExprs.insert(OSR); + } +} + +void ConstraintSystem::shrink(Expr *expr) { + if (getASTContext().TypeCheckerOpts.SolverDisableShrink) + return; + + using DomainMap = llvm::SmallDenseMap>; + + // A collection of original domains of all of the expressions, + // so they can be restored in case of failure. + DomainMap domains; + + struct ExprCollector : public ASTWalker { + Expr *PrimaryExpr; + + // The primary constraint system. + ConstraintSystem &CS; + + // All of the sub-expressions which are suitable to be solved + // separately from the main system e.g. binary expressions, collections, + // function calls, coercions etc. + llvm::SmallVector Candidates; + + // Counts the number of overload sets present in the tree so far. + // Note that the traversal is depth-first. + llvm::SmallVector, 4> ApplyExprs; + + // A collection of original domains of all of the expressions, + // so they can be restored in case of failure. + DomainMap &Domains; + + ExprCollector(Expr *expr, ConstraintSystem &cs, DomainMap &domains) + : PrimaryExpr(expr), CS(cs), Domains(domains) {} + + MacroWalking getMacroWalkingBehavior() const override { + return MacroWalking::Arguments; + } + + PreWalkResult walkToExprPre(Expr *expr) override { + // A dictionary expression is just a set of tuples; try to solve ones + // that have overload sets. + if (auto collectionExpr = dyn_cast(expr)) { + visitCollectionExpr(collectionExpr, + CS.getContextualType(expr, /*forConstraint=*/false), + CS.getContextualTypePurpose(expr)); + // Don't try to walk into the dictionary. + return Action::SkipNode(expr); + } + + // Let's not attempt to type-check closures or expressions + // which constrain closures, because they require special handling + // when dealing with context and parameters declarations. + if (isa(expr)) { + return Action::SkipNode(expr); + } + + // Similar to 'ClosureExpr', 'TapExpr' has a 'VarDecl' the type of which + // is determined by type checking the parent interpolated string literal. + if (isa(expr)) { + return Action::SkipNode(expr); + } + + // Same as TapExpr and ClosureExpr, we'll handle SingleValueStmtExprs + // separately. + if (isa(expr)) + return Action::SkipNode(expr); + + if (auto coerceExpr = dyn_cast(expr)) { + if (coerceExpr->isLiteralInit()) + ApplyExprs.push_back({coerceExpr, 1}); + visitCoerceExpr(coerceExpr); + return Action::SkipNode(expr); + } + + if (auto OSR = dyn_cast(expr)) { + Domains[OSR] = OSR->getDecls(); + } + + if (auto applyExpr = dyn_cast(expr)) { + auto func = applyExpr->getFn(); + // Let's record this function application for post-processing + // as well as if it contains overload set, see walkToExprPost. + ApplyExprs.push_back( + {applyExpr, isa(func) || isa(func)}); + } + + return Action::Continue(expr); + } + + /// Determine whether this is an arithmetic expression comprised entirely + /// of literals. + static bool isArithmeticExprOfLiterals(Expr *expr) { + expr = expr->getSemanticsProvidingExpr(); + + if (auto prefix = dyn_cast(expr)) + return isArithmeticExprOfLiterals(prefix->getOperand()); + + if (auto postfix = dyn_cast(expr)) + return isArithmeticExprOfLiterals(postfix->getOperand()); + + if (auto binary = dyn_cast(expr)) + return isArithmeticExprOfLiterals(binary->getLHS()) && + isArithmeticExprOfLiterals(binary->getRHS()); + + return isa(expr) || isa(expr); + } + + PostWalkResult walkToExprPost(Expr *expr) override { + auto isSrcOfPrimaryAssignment = [&](Expr *expr) -> bool { + if (auto *AE = dyn_cast(PrimaryExpr)) + return expr == AE->getSrc(); + return false; + }; + + if (expr == PrimaryExpr || isSrcOfPrimaryAssignment(expr)) { + // If this is primary expression and there are no candidates + // to be solved, let's not record it, because it's going to be + // solved regardless. + if (Candidates.empty()) + return Action::Continue(expr); + + auto contextualType = CS.getContextualType(expr, + /*forConstraint=*/false); + // If there is a contextual type set for this expression. + if (!contextualType.isNull()) { + Candidates.push_back(Candidate(CS, PrimaryExpr, contextualType, + CS.getContextualTypePurpose(expr))); + return Action::Continue(expr); + } + + // Or it's a function application or assignment with other candidates + // present. Assignment should be easy to solve because we'd get a + // contextual type from the destination expression, otherwise shrink + // might produce incorrect results without considering aforementioned + // destination type. + if (isa(expr) || isa(expr)) { + Candidates.push_back(Candidate(CS, PrimaryExpr)); + return Action::Continue(expr); + } + } + + if (!isa(expr)) + return Action::Continue(expr); + + unsigned numOverloadSets = 0; + // Let's count how many overload sets do we have. + while (!ApplyExprs.empty()) { + auto &application = ApplyExprs.back(); + auto applyExpr = application.first; + + // Add overload sets tracked by current expression. + numOverloadSets += application.second; + ApplyExprs.pop_back(); + + // We've found the current expression, so record the number of + // overloads. + if (expr == applyExpr) { + ApplyExprs.push_back({applyExpr, numOverloadSets}); + break; + } + } + + // If there are fewer than two overloads in the chain + // there is no point of solving this expression, + // because we won't be able to reduce its domain. + if (numOverloadSets > 1 && !isArithmeticExprOfLiterals(expr)) + Candidates.push_back(Candidate(CS, expr)); + + return Action::Continue(expr); + } + + private: + /// Extract type of the element from given collection type. + /// + /// \param collection The type of the collection container. + /// + /// \returns Null type, ErrorType or UnresolvedType on failure, + /// properly constructed type otherwise. + Type extractElementType(Type collection) { + auto &ctx = CS.getASTContext(); + if (!collection || collection->hasError()) + return collection; + + auto base = collection.getPointer(); + auto isInvalidType = [](Type type) -> bool { + return type.isNull() || type->hasUnresolvedType() || + type->hasError(); + }; + + // Array type. + if (auto array = dyn_cast(base)) { + auto elementType = array->getBaseType(); + // If base type is invalid let's return error type. + return elementType; + } + + // Map or Set or any other associated collection type. + if (auto boundGeneric = dyn_cast(base)) { + if (boundGeneric->hasUnresolvedType()) + return boundGeneric; + + llvm::SmallVector params; + for (auto &type : boundGeneric->getGenericArgs()) { + // One of the generic arguments in invalid or unresolved. + if (isInvalidType(type)) + return type; + + params.push_back(type); + } + + // If there is just one parameter, let's return it directly. + if (params.size() == 1) + return params[0].getType(); + + return TupleType::get(params, ctx); + } + + return Type(); + } + + bool isSuitableCollection(TypeRepr *collectionTypeRepr) { + // Only generic identifier, array or dictionary. + switch (collectionTypeRepr->getKind()) { + case TypeReprKind::UnqualifiedIdent: + return cast(collectionTypeRepr) + ->hasGenericArgList(); + + case TypeReprKind::Array: + case TypeReprKind::Dictionary: + return true; + + default: + break; + } + + return false; + } + + void visitCoerceExpr(CoerceExpr *coerceExpr) { + auto subExpr = coerceExpr->getSubExpr(); + // Coerce expression is valid only if it has sub-expression. + if (!subExpr) return; + + unsigned numOverloadSets = 0; + subExpr->forEachChildExpr([&](Expr *childExpr) -> Expr * { + if (isa(childExpr)) { + ++numOverloadSets; + return childExpr; + } + + if (auto nestedCoerceExpr = dyn_cast(childExpr)) { + visitCoerceExpr(nestedCoerceExpr); + // Don't walk inside of nested coercion expression directly, + // that is be done by recursive call to visitCoerceExpr. + return nullptr; + } + + // If sub-expression we are trying to coerce to type is a collection, + // let's allow collector discover it with assigned contextual type + // of coercion, which allows collections to be solved in parts. + if (auto collectionExpr = dyn_cast(childExpr)) { + auto *const typeRepr = coerceExpr->getCastTypeRepr(); + + if (typeRepr && isSuitableCollection(typeRepr)) { + const auto coercionType = TypeResolution::resolveContextualType( + typeRepr, CS.DC, std::nullopt, + // FIXME: Should we really be unconditionally complaining + // about unbound generics and placeholders here? For + // example: + // let foo: [Array] = [[0], [1], [2]] as [Array] + // let foo: [Array] = [[0], [1], [2]] as [Array<_>] + /*unboundTyOpener*/ nullptr, /*placeholderHandler*/ nullptr, + /*packElementOpener*/ nullptr); + + // Looks like coercion type is invalid, let's skip this sub-tree. + if (coercionType->hasError()) + return nullptr; + + // Visit collection expression inline. + visitCollectionExpr(collectionExpr, coercionType, + CTP_CoerceOperand); + } + } + + return childExpr; + }); + + // It's going to be inefficient to try and solve + // coercion in parts, so let's just make it a candidate directly, + // if it contains at least a single overload set. + + if (numOverloadSets > 0) + Candidates.push_back(Candidate(CS, coerceExpr)); + } + + void visitCollectionExpr(CollectionExpr *collectionExpr, + Type contextualType = Type(), + ContextualTypePurpose CTP = CTP_Unused) { + // If there is a contextual type set for this collection, + // let's propagate it to the candidate. + if (!contextualType.isNull()) { + auto elementType = extractElementType(contextualType); + // If we couldn't deduce element type for the collection, let's + // not attempt to solve it. + if (!elementType || + elementType->hasError() || + elementType->hasUnresolvedType()) + return; + + contextualType = elementType; + } + + for (auto element : collectionExpr->getElements()) { + unsigned numOverloads = 0; + element->walk(OverloadSetCounter(numOverloads)); + + // There are no overload sets in the element; skip it. + if (numOverloads == 0) + continue; + + // Record each of the collection elements, which passed + // number of overload sets rule, as a candidate for solving + // with contextual type of the collection. + Candidates.push_back(Candidate(CS, element, contextualType, CTP)); + } + } + }; + + ExprCollector collector(expr, *this, domains); + + // Collect all of the binary/unary and call sub-expressions + // so we can start solving them separately. + expr->walk(collector); + + llvm::SmallSetVector shrunkExprs; + for (auto &candidate : collector.Candidates) { + // If there are no results, let's forget everything we know about the + // system so far. This actually is ok, because some of the expressions + // might require manual salvaging. + if (candidate.solve(shrunkExprs)) { + // Let's restore all of the original OSR domains for this sub-expression, + // this means that we can still make forward progress with solving of the + // top sub-expressions. + candidate.getExpr()->forEachChildExpr([&](Expr *childExpr) -> Expr * { + if (auto OSR = dyn_cast(childExpr)) { + auto domain = domains.find(OSR); + if (domain == domains.end()) + return childExpr; + + OSR->setDecls(domain->getSecond()); + shrunkExprs.remove(OSR); + } + + return childExpr; + }); + } + } + + // Once "shrinking" is done let's re-allocate final version of + // the candidate list to the permanent arena, so it could + // survive even after primary constraint system is destroyed. + for (auto &OSR : shrunkExprs) { + auto choices = OSR->getDecls(); + auto decls = + getASTContext().AllocateUninitialized(choices.size()); + + std::uninitialized_copy(choices.begin(), choices.end(), decls.begin()); + OSR->setDecls(decls); + } +} + static bool debugConstraintSolverForTarget(ASTContext &C, SyntacticElementTarget target) { if (C.TypeCheckerOpts.DebugConstraintSolver) @@ -1153,6 +1703,8 @@ bool ConstraintSystem::solveForCodeCompletion( // Set up the expression type checker timer. startExpressionTimer(expr); + + shrink(expr); } if (isDebugMode()) { @@ -1285,6 +1837,62 @@ ConstraintSystem::filterDisjunction( return SolutionKind::Unsolved; } +// Attempt to find a disjunction of bind constraints where all options +// in the disjunction are binding the same type variable. +// +// Prefer disjunctions where the bound type variable is also the +// right-hand side of a conversion constraint, since having a concrete +// type that we're converting to can make it possible to split the +// constraint system into multiple ones. +static Constraint *selectBestBindingDisjunction( + ConstraintSystem &cs, SmallVectorImpl &disjunctions) { + + if (disjunctions.empty()) + return nullptr; + + auto getAsTypeVar = [&cs](Type type) { + return cs.simplifyType(type)->getRValueType()->getAs(); + }; + + Constraint *firstBindDisjunction = nullptr; + for (auto *disjunction : disjunctions) { + auto choices = disjunction->getNestedConstraints(); + assert(!choices.empty()); + + auto *choice = choices.front(); + if (choice->getKind() != ConstraintKind::Bind) + continue; + + // We can judge disjunction based on the single choice + // because all of choices (of bind overload set) should + // have the same left-hand side. + // Only do this for simple type variable bindings, not for + // bindings like: ($T1) -> $T2 bind String -> Int + auto *typeVar = getAsTypeVar(choice->getFirstType()); + if (!typeVar) + continue; + + if (!firstBindDisjunction) + firstBindDisjunction = disjunction; + + auto constraints = cs.getConstraintGraph().gatherConstraints( + typeVar, ConstraintGraph::GatheringKind::EquivalenceClass, + [](Constraint *constraint) { + return constraint->getKind() == ConstraintKind::Conversion; + }); + + for (auto *constraint : constraints) { + if (typeVar == getAsTypeVar(constraint->getSecondType())) + return disjunction; + } + } + + // If we had any binding disjunctions, return the first of + // those. These ensure that we attempt to bind types earlier than + // trying the elements of other disjunctions, which can often mean + // we fail faster. + return firstBindDisjunction; +} std::optional> ConstraintSystem::findConstraintThroughOptionals( @@ -1390,27 +1998,6 @@ tryOptimizeGenericDisjunction(ConstraintSystem &cs, Constraint *disjunction, return nullptr; } - // Don't attempt this optimization if call has number literals. - // This is intended to narrowly fix situations like: - // - // func test(_: T) { ... } - // func test(_: T) { ... } - // - // test(42) - // - // The call should use `` overload even though the - // `` is a more specialized version because - // selecting `` doesn't introduce non-default literal - // types. - if (auto *argFnType = cs.getAppliedDisjunctionArgumentFunction(disjunction)) { - if (llvm::any_of( - argFnType->getParams(), [](const AnyFunctionType::Param ¶m) { - auto *typeVar = param.getPlainType()->getAs(); - return typeVar && typeVar->getImpl().isNumberLiteralType(); - })) - return nullptr; - } - llvm::SmallVector choices; for (auto *choice : constraints) { if (choices.size() > 2) @@ -1781,6 +2368,61 @@ void DisjunctionChoiceProducer::partitionDisjunction( assert(Ordering.size() == Choices.size()); } +Constraint *ConstraintSystem::selectDisjunction() { + SmallVector disjunctions; + + collectDisjunctions(disjunctions); + if (disjunctions.empty()) + return nullptr; + + if (auto *disjunction = selectBestBindingDisjunction(*this, disjunctions)) + return disjunction; + + // Pick the disjunction with the smallest number of favored, then active choices. + auto cs = this; + auto minDisjunction = std::min_element(disjunctions.begin(), disjunctions.end(), + [&](Constraint *first, Constraint *second) -> bool { + unsigned firstActive = first->countActiveNestedConstraints(); + unsigned secondActive = second->countActiveNestedConstraints(); + unsigned firstFavored = first->countFavoredNestedConstraints(); + unsigned secondFavored = second->countFavoredNestedConstraints(); + + if (!isOperatorDisjunction(first) || !isOperatorDisjunction(second)) + return firstActive < secondActive; + + if (firstFavored == secondFavored) { + // Look for additional choices that are "favored" + SmallVector firstExisting; + SmallVector secondExisting; + + existingOperatorBindingsForDisjunction(*cs, first->getNestedConstraints(), firstExisting); + firstFavored += firstExisting.size(); + existingOperatorBindingsForDisjunction(*cs, second->getNestedConstraints(), secondExisting); + secondFavored += secondExisting.size(); + } + + // Everything else equal, choose the disjunction with the greatest + // number of resolved argument types. The number of resolved argument + // types is always zero for disjunctions that don't represent applied + // overloads. + if (firstFavored == secondFavored) { + if (firstActive != secondActive) + return firstActive < secondActive; + + return (first->countResolvedArgumentTypes(*this) > second->countResolvedArgumentTypes(*this)); + } + + firstFavored = firstFavored ? firstFavored : firstActive; + secondFavored = secondFavored ? secondFavored : secondActive; + return firstFavored < secondFavored; + }); + + if (minDisjunction != disjunctions.end()) + return *minDisjunction; + + return nullptr; +} + Constraint *ConstraintSystem::selectConjunction() { SmallVector conjunctions; for (auto &constraint : InactiveConstraints) { diff --git a/lib/Sema/CSStep.cpp b/lib/Sema/CSStep.cpp index 77632d2a8549d..14ce02e330d51 100644 --- a/lib/Sema/CSStep.cpp +++ b/lib/Sema/CSStep.cpp @@ -366,7 +366,7 @@ StepResult ComponentStep::take(bool prevFailed) { } }); - auto disjunction = CS.selectDisjunction(); + auto *disjunction = CS.selectDisjunction(); auto *conjunction = CS.selectConjunction(); if (CS.isDebugMode()) { @@ -409,8 +409,7 @@ StepResult ComponentStep::take(bool prevFailed) { // Bindings usually happen first, but sometimes we want to prioritize a // disjunction or conjunction. if (bestBindings) { - if (disjunction && - !bestBindings->favoredOverDisjunction(disjunction->first)) + if (disjunction && !bestBindings->favoredOverDisjunction(disjunction)) return StepKind::Disjunction; if (conjunction && !bestBindings->favoredOverConjunction(conjunction)) @@ -433,9 +432,9 @@ StepResult ComponentStep::take(bool prevFailed) { return suspend( std::make_unique(*bestBindings, Solutions)); case StepKind::Disjunction: { - CS.retireConstraint(disjunction->first); + CS.retireConstraint(disjunction); return suspend( - std::make_unique(CS, *disjunction, Solutions)); + std::make_unique(CS, disjunction, Solutions)); } case StepKind::Conjunction: { CS.retireConstraint(conjunction); diff --git a/lib/Sema/CSStep.h b/lib/Sema/CSStep.h index a3e75b93f0136..3b8c39e4a015d 100644 --- a/lib/Sema/CSStep.h +++ b/lib/Sema/CSStep.h @@ -671,28 +671,26 @@ class TypeVariableStep final : public BindingStep { class DisjunctionStep final : public BindingStep { Constraint *Disjunction; + SmallVector DisabledChoices; + std::optional BestNonGenericScore; std::optional> LastSolvedChoice; public: - DisjunctionStep( - ConstraintSystem &cs, - std::pair> &disjunction, - SmallVectorImpl &solutions) - : DisjunctionStep(cs, disjunction.first, disjunction.second, solutions) {} - DisjunctionStep(ConstraintSystem &cs, Constraint *disjunction, - llvm::TinyPtrVector &favoredChoices, SmallVectorImpl &solutions) - : BindingStep(cs, {cs, disjunction, favoredChoices}, solutions), - Disjunction(disjunction) { + : BindingStep(cs, {cs, disjunction}, solutions), Disjunction(disjunction) { assert(Disjunction->getKind() == ConstraintKind::Disjunction); + pruneOverloadSet(Disjunction); ++cs.solverState->NumDisjunctions; } ~DisjunctionStep() override { // Rewind back any changes left after attempting last choice. ActiveChoice.reset(); + // Re-enable previously disabled overload choices. + for (auto *choice : DisabledChoices) + choice->setEnabled(); } StepResult resume(bool prevFailed) override; @@ -745,6 +743,46 @@ class DisjunctionStep final : public BindingStep { /// simplified further, false otherwise. bool attempt(const DisjunctionChoice &choice) override; + // Check if selected disjunction has a representative + // this might happen when there are multiple binary operators + // chained together. If so, disable choices which differ + // from currently selected representative. + void pruneOverloadSet(Constraint *disjunction) { + auto *choice = disjunction->getNestedConstraints().front(); + if (choice->getKind() != ConstraintKind::BindOverload) + return; + + auto *typeVar = choice->getFirstType()->getAs(); + if (!typeVar) + return; + + auto *repr = typeVar->getImpl().getRepresentative(nullptr); + if (!repr || repr == typeVar) + return; + + for (auto overload : CS.getResolvedOverloads()) { + auto resolved = overload.second; + if (!resolved.boundType->isEqual(repr)) + continue; + + auto &representative = resolved.choice; + if (!representative.isDecl()) + return; + + // Disable all of the overload choices which are different from + // the one which is currently picked for representative. + for (auto *constraint : disjunction->getNestedConstraints()) { + auto choice = constraint->getOverloadChoice(); + if (!choice.isDecl() || choice.getDecl() == representative.getDecl()) + continue; + + constraint->setDisabled(); + DisabledChoices.push_back(constraint); + } + break; + } + }; + // Figure out which of the solutions has the smallest score. static std::optional getBestScore(SmallVectorImpl &solutions) { diff --git a/lib/Sema/Constraint.cpp b/lib/Sema/Constraint.cpp index e44f4873a1f8f..be1a062bc5c5b 100644 --- a/lib/Sema/Constraint.cpp +++ b/lib/Sema/Constraint.cpp @@ -703,6 +703,17 @@ gatherReferencedTypeVars(Constraint *constraint, } } +unsigned Constraint::countResolvedArgumentTypes(ConstraintSystem &cs) const { + auto *argumentFuncType = cs.getAppliedDisjunctionArgumentFunction(this); + if (!argumentFuncType) + return 0; + + return llvm::count_if(argumentFuncType->getParams(), [&](const AnyFunctionType::Param arg) { + auto argType = cs.getFixedTypeRecursive(arg.getPlainType(), /*wantRValue=*/true); + return !argType->isTypeVariableOrMember(); + }); +} + bool Constraint::isExplicitConversion() const { assert(Kind == ConstraintKind::Disjunction); diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index cb44d7ef92fcb..b1e574ed2ee99 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -204,14 +204,6 @@ bool TypeVariableType::Implementation::isCollectionLiteralType() const { locator->directlyAt()); } -bool TypeVariableType::Implementation::isNumberLiteralType() const { - return locator && locator->directlyAt(); -} - -bool TypeVariableType::Implementation::isFunctionResult() const { - return locator && locator->isLastElement(); -} - void *operator new(size_t bytes, ConstraintSystem& cs, size_t alignment) { return cs.getAllocator().Allocate(bytes, alignment); @@ -458,6 +450,10 @@ TypeChecker::typeCheckTarget(SyntacticElementTarget &target, // diagnostics and is a hint for various performance optimizations. cs.setContextualInfo(expr, target.getExprContextualTypeInfo()); + // Try to shrink the system by reducing disjunction domains. This + // goes through every sub-expression and generate its own sub-system, to + // try to reduce the domains of those subexpressions. + cs.shrink(expr); target.setExpr(expr); } diff --git a/lib/Sema/TypeOfReference.cpp b/lib/Sema/TypeOfReference.cpp index d60bac95fbc6a..44110d5927e6d 100644 --- a/lib/Sema/TypeOfReference.cpp +++ b/lib/Sema/TypeOfReference.cpp @@ -1834,15 +1834,11 @@ Type ConstraintSystem::getEffectiveOverloadType(ConstraintLocator *locator, type, var, useDC, GetClosureType{*this}, ClosureIsolatedByPreconcurrency{*this}); } else if (isa(decl) || isa(decl)) { - if (decl->isInstanceMember()) { - auto baseTy = overload.getBaseType(); - if (!baseTy) - return Type(); - - baseTy = baseTy->getRValueType(); - if (!baseTy->getAnyNominal() && !baseTy->is()) - return Type(); - } + if (decl->isInstanceMember() && + (!overload.getBaseType() || + (!overload.getBaseType()->getAnyNominal() && + !overload.getBaseType()->is()))) + return Type(); // Cope with 'Self' returns. if (!decl->getDeclContext()->getSelfProtocolDecl()) { diff --git a/test/Constraints/async.swift b/test/Constraints/async.swift index 9985f169c0a22..52f7a44e46d55 100644 --- a/test/Constraints/async.swift +++ b/test/Constraints/async.swift @@ -212,9 +212,10 @@ func test_async_calls_in_async_context(v: Int) async { } // Only implicit `.init` should be accepted with a warning due type-checker previously picking an incorrect overload. - _ = Test(v) // expected-warning {{expression is 'async' but is not marked with 'await'; this is an error in the Swift 6 language mode}} expected-note {{call is 'async'}} + // FIXME: This should produce a warning once type-checker performance hacks are removed. + _ = Test(v) // Temporary okay _ = Test.init(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note {{call is 'async'}} Test.test(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note {{call is 'async'}} - Test(v).test(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note 2 {{call is 'async'}} + Test(v).test(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note {{call is 'async'}} } diff --git a/test/Constraints/casts_swift6.swift b/test/Constraints/casts_swift6.swift index 17cbd89100741..5161a493e9dbd 100644 --- a/test/Constraints/casts_swift6.swift +++ b/test/Constraints/casts_swift6.swift @@ -25,9 +25,9 @@ func test_compatibility_coercions(_ arr: [Int], _ optArr: [Int]?, _ dict: [Strin // Make sure we error on the following in Swift 6 mode. _ = id(arr) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[Int]' vs. '[String]')}} - _ = (arr ?? []) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[Int]' vs. '[String]')}} - _ = (arr ?? [] ?? []) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[Int]' vs. '[String]')}} - // expected-error@-1{{conflicting arguments to generic parameter 'T' ('[Int]' vs. '[String]')}} + _ = (arr ?? []) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[String]' vs. '[Int]')}} + _ = (arr ?? [] ?? []) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[String]' vs. '[Int]')}} + // expected-error@-1{{conflicting arguments to generic parameter 'T' ('[String]' vs. '[Int]')}} _ = (optArr ?? []) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[Int]' vs. '[String]'}} _ = (arr ?? []) as [String]? // expected-error {{'[Int]' is not convertible to '[String]?'}} diff --git a/test/Constraints/common_type.swift b/test/Constraints/common_type.swift index bad4415cf63f1..32f3bf3c8bb35 100644 --- a/test/Constraints/common_type.swift +++ b/test/Constraints/common_type.swift @@ -1,8 +1,6 @@ // RUN: %target-typecheck-verify-swift -debug-constraints 2>%t.err // RUN: %FileCheck %s < %t.err -// REQUIRES: needs_adjustment_for_new_favoring - struct X { func g(_: Int) -> Int { return 0 } func g(_: Double) -> Int { return 0 } diff --git a/test/Constraints/implicit_double_cgfloat_conversion.swift b/test/Constraints/implicit_double_cgfloat_conversion.swift index b34d8a88e566d..32126db0864d0 100644 --- a/test/Constraints/implicit_double_cgfloat_conversion.swift +++ b/test/Constraints/implicit_double_cgfloat_conversion.swift @@ -342,42 +342,3 @@ func test_init_validation() { } } } - -do { - struct G { - init(_: T) {} - } - - func round(_: Double) -> Double {} - func round(_: T) -> T {} - - func test_cgfloat_over_double(withColors colors: Int, size: CGSize) -> G { - let g = G(1.0 / CGFloat(colors)) - return g // Ok - } - - func test_no_ambiguity(width: Int, height: Int) -> CGFloat { - let v = round(CGFloat(width / height) * 10) / 10.0 - return v // Ok - } -} - -func test_cgfloat_operator_is_attempted_with_literal_arguments(v: CGFloat?) { - let ratio = v ?? (2.0 / 16.0) - let _: CGFloat = ratio // Ok -} - -// Make sure that optimizer doesn't favor CGFloat -> Double conversion -// in presence of CGFloat initializer, otherwise it could lead to ambiguities. -func test_explicit_cgfloat_use_avoids_ambiguity(v: Int) { - func test(_: CGFloat) -> CGFloat { 0 } - func test(_: Double) -> Double { 0 } - - func hasCGFloatElement(_: C) where C.Element == CGFloat {} - - let arr = [test(CGFloat(v))] - hasCGFloatElement(arr) // Ok - - var total = 0.0 // This is Double by default - total += test(CGFloat(v)) + CGFloat(v) // Ok -} diff --git a/test/Constraints/member_import_visibility.swift b/test/Constraints/member_import_visibility.swift deleted file mode 100644 index cda1c01513a7a..0000000000000 --- a/test/Constraints/member_import_visibility.swift +++ /dev/null @@ -1,39 +0,0 @@ -// RUN: %empty-directory(%t) -// RUN: %empty-directory(%t/src) -// RUN: split-file %s %t/src - -/// Build the library A -// RUN: %target-swift-frontend -emit-module %t/src/A.swift \ -// RUN: -module-name A \ -// RUN: -emit-module-path %t/A.swiftmodule - -/// Build the library B -// RUN: %target-swift-frontend -I %t -emit-module %t/src/B.swift \ -// RUN: -module-name B \ -// RUN: -emit-module-path %t/B.swiftmodule - -// RUN: %target-swift-frontend -typecheck -I %t %t/src/Main.swift %t/src/Other.swift -enable-upcoming-feature MemberImportVisibility - -// REQUIRES: swift_feature_MemberImportVisibility - -//--- A.swift -public struct Test { - public init(a: Double) { } -} - -//--- B.swift -import A - -extension Test { - public init(a: Int) { fatalError() } -} - -//--- Main.swift -import A - -func test() { - _ = Test(a: 0) // Ok, selects the overload that takes Double. -} - -//--- Other.swift -import B diff --git a/test/Constraints/old_hack_related_ambiguities.swift b/test/Constraints/old_hack_related_ambiguities.swift deleted file mode 100644 index da8da45cc76dc..0000000000000 --- a/test/Constraints/old_hack_related_ambiguities.swift +++ /dev/null @@ -1,259 +0,0 @@ -// RUN: %target-typecheck-verify-swift - -func entity(_: Int) -> Int { - 0 -} - -struct Test { - func test(_ v: Int) -> Int { v } - func test(_ v: Int?) -> Int? { v } -} - -func test_ternary_literal(v: Test) -> Int? { - true ? v.test(0) : nil // Ok -} - -func test_ternary(v: Test) -> Int? { - true ? v.test(entity(0)) : nil // Ok -} - -do { - struct TestFloat { - func test(_ v: Float) -> Float { v } // expected-note {{found this candidate}} - func test(_ v: Float?) -> Float? { v } // expected-note {{found this candidate}} - } - - func test_ternary_non_default_literal(v: TestFloat) -> Float? { - true ? v.test(1.0) : nil // expected-error {{ambiguous use of 'test'}} - } -} - -do { - struct Test { - init(a: Int, b: Int = 0) throws {} - init?(a: Int?) {} - } - - func test(v: Int) -> Test? { - return Test(a: v) // Ok - } -} - -// error: initializer for conditional binding must have Optional type, not 'S' -do { - struct S { - let n: Int - - func test(v: String) -> Int { } - func test(v: String, flag: Bool = false) -> Int? { } - - - func verify(v: String) -> Int? { - guard let _ = test(v: v) else { // Ok - return nil - } - return 0 - } - } - - func f(_: String, _ p: Bool = false) -> S? { - nil - } - - func f(_ x: String) -> S { - fatalError() - } - - func g(_ x: String) -> Int? { - guard let y = f(x) else { - return nil - } - return y.n - } -} - -// ambiguities related to ~= -protocol _Error: Error {} - -extension _Error { - public static func ~=(lhs: Self, rhs: Self) -> Bool { - false - } - - public static func ~=(lhs: Error, rhs: Self) -> Bool { - false - } - - public static func ~=(lhs: Self, rhs: Error) -> Bool { - false - } -} - -enum CustomError { - case A -} - -extension CustomError: _Error {} - -func f(e: CustomError) { - if e ~= CustomError.A {} -} - -// Generic overload should be preferred over concrete one because the latter is non-default literal -struct Pattern {} - -func ~= (pattern: Pattern, value: String) -> Bool { - return false -} - -extension Pattern: ExpressibleByStringLiteral { - init(stringLiteral value: String) {} -} - -func test_default_tilda(v: String) { - _ = "hi" ~= v // Ok -} - -struct UUID {} - -struct LogKey { - init(base: some CustomStringConvertible, foo: Int = 0) { - } - - init(base: UUID, foo: Int = 0) { - } -} - -@available(swift 99) -extension LogKey { - init(base: String?) { - } - - init(base: UUID?) { - } -} - -func test_that_unavailable_init_is_not_used(x: String?) { - _ = LogKey(base: x ?? "??") -} - -// error: value of optional type 'UID?' must be unwrapped to a value of type 'UID' -struct S: Comparable { - static func <(lhs: Self, rhs: Self) -> Bool { - false - } -} - -func max(_ a: S?, _ b: S?) -> S? { - nil -} - -func test_stdlib_max_selection(s: S) -> S { - let new = max(s, s) - return new // Ok -} - -// error: initializer for conditional binding must have Optional type, not 'UnsafeMutablePointer' -do { - struct TestPointerConversions { - var p: UnsafeMutableRawPointer { get { fatalError() } } - - func f(_ p: UnsafeMutableRawPointer) { - guard let x = UnsafeMutablePointer(OpaquePointer(self.p)) else { - return - } - _ = x - - guard let x = UnsafeMutablePointer(OpaquePointer(p)) else { - return - } - _ = x - } - } -} - -// error: initializer 'init(_:)' requires that 'T' conform to 'BinaryInteger' -do { - struct Config { - subscript(_ key: String) -> T? { nil } - subscript(_ key: String) -> Any? { nil } - } - - struct S { - init(maxQueueDepth: UInt) {} - } - - func f(config: Config) { - let maxQueueDepth = config["hi"] ?? 256 - _ = S(maxQueueDepth: UInt(maxQueueDepth)) - } -} - -// `tryOptimizeGenericDisjunction` is too aggressive sometimes, make sure that `` -// overload is _not_ selected in this case. -do { - func test(_ expression1: @autoclosure () throws -> T, accuracy: T) -> T {} - func test(_ expression1: @autoclosure () throws -> T, accuracy: T) -> T {} - - let result = test(10, accuracy: 1) - let _: Int = result -} - -// swift-distributed-tracing snippet that relies on old hack behavior. -protocol TracerInstant { -} - -extension Int: TracerInstant {} - -do { - enum SpanKind { - case `internal` - } - - func withSpan( - _ operationName: String, - at instant: @autoclosure () -> Instant, - context: @autoclosure () -> Int = 0, - ofKind kind: SpanKind = .internal - ) {} - - func withSpan( - _ operationName: String, - context: @autoclosure () -> Int = 0, - ofKind kind: SpanKind = .internal, - at instant: @autoclosure () -> some TracerInstant = 42 - ) {} - - withSpan("", at: 0) // Ok -} - -protocol ForAssert { - var isEmpty: Bool { get } -} - -extension ForAssert { - var isEmpty: Bool { false } -} - -do { - func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) {} - func assert(_ condition: Bool, _ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) {} - func assert(_ condition: Bool, file: StaticString = #fileID, line: UInt = #line) {} - - struct S : ForAssert { - var isEmpty: Bool { false } - } - - func test(s: S) { - assert(s.isEmpty, "") // Ok - } -} - -extension Double { - public static func * (left: Float, right: Double) -> Double { 0 } -} - -func test_non_default_literal_use(arg: Float) { - let v = arg * 2.0 // shouldn't use `(Float, Double) -> Double` overload - let _: Float = v // Ok -} diff --git a/test/Constraints/operator.swift b/test/Constraints/operator.swift index d12bcbbc20a04..4770481c95e30 100644 --- a/test/Constraints/operator.swift +++ b/test/Constraints/operator.swift @@ -329,66 +329,3 @@ enum I60954 { } init?(_ string: S) where S: StringProtocol {} // expected-note{{where 'S' = 'I60954'}} } - -infix operator <<<>>> : DefaultPrecedence - -protocol P5 { -} - -struct Expr : P6 {} - -protocol P6: P5 { -} - -extension P6 { - public static func <<<>>> (lhs: Self, rhs: (any P5)?) -> Expr { Expr() } - public static func <<<>>> (lhs: (any P5)?, rhs: Self) -> Expr { Expr() } - public static func <<<>>> (lhs: Self, rhs: some P6) -> Expr { Expr() } - - public static prefix func ! (value: Self) -> Expr { - Expr() - } -} - -extension P6 { - public static func != (lhs: Self, rhs: some P6) -> Expr { - !(lhs <<<>>> rhs) // Ok - } -} - -do { - struct Value : P6 { - } - - struct Column: P6 { - } - - func test(col: Column, val: Value) -> Expr { - col <<<>>> val // Ok - } - - func test(col: Column, val: some P6) -> Expr { - col <<<>>> val // Ok - } - - func test(col: some P6, val: Value) -> Expr { - col <<<>>> val // Ok - } -} - -// Make sure that ?? selects an overload that doesn't produce an optional. -do { - class Obj { - var x: String! - } - - class Child : Obj { - func x() -> String? { nil } - static func x(_: Int) -> String { "" } - } - - func test(arr: [Child], v: String, defaultV: Child) -> Child { - let result = arr.first { $0.x == v } ?? defaultV - return result // Ok - } -} diff --git a/test/IDE/complete_operators.swift b/test/IDE/complete_operators.swift index 483d3cc6225d8..a349fd4ecf8da 100644 --- a/test/IDE/complete_operators.swift +++ b/test/IDE/complete_operators.swift @@ -52,7 +52,7 @@ func testPostfix6() { func testPostfix7() { 1 + 2 * 3.0#^POSTFIX_7^# } -// POSTFIX_7: Decl[PostfixOperatorFunction]/CurrModule/TypeRelation[Convertible]: ***[#Double#] +// POSTFIX_7: Decl[PostfixOperatorFunction]/CurrModule: ***[#Double#] func testPostfix8(x: S) { x#^POSTFIX_8^# diff --git a/test/expr/expressions.swift b/test/expr/expressions.swift index 4a4d8c3232ce8..d7ddc5fc0c765 100644 --- a/test/expr/expressions.swift +++ b/test/expr/expressions.swift @@ -758,10 +758,10 @@ func invalidDictionaryLiteral() { //===----------------------------------------------------------------------===// // nil/metatype comparisons //===----------------------------------------------------------------------===// -_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns false}} -_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns false}} -_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns true}} -_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns true}} +_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns false}} +_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns false}} +_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}} +_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}} _ = Int.self == .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}} _ = .none == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}} diff --git a/validation-test/Sema/issue78371.swift b/validation-test/Sema/issue78371.swift deleted file mode 100644 index e986ad173e291..0000000000000 --- a/validation-test/Sema/issue78371.swift +++ /dev/null @@ -1,22 +0,0 @@ -// RUN: %target-typecheck-verify-swift - -// https://github.com/swiftlang/swift/issues/78371 - -// Note that the test has to use a standard operator because -// custom ones are not optimized. - -struct Scalar : Equatable { - init(_: UInt8) {} - init?(_: Int) {} -} - -func ==(_: Scalar, _: Scalar) -> Bool { } - -extension Optional where Wrapped == Scalar { - static func ==(_: Wrapped?, _: Wrapped?) -> Wrapped { } -} - -func test(a: Scalar) { - let result = a == Scalar(0x07FD) - let _: Scalar = result // Ok -} diff --git a/validation-test/Sema/rdar143799118.swift b/validation-test/Sema/rdar143799118.swift deleted file mode 100644 index 7ed8a24e02026..0000000000000 --- a/validation-test/Sema/rdar143799118.swift +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: %target-typecheck-verify-swift -target %target-swift-5.1-abi-triple - -func test1(v: Int!) -> [Any]! { nil } -// This is important because it defeats old solver hack that -// checked the number of matching overloads purely based on -// how many parameters there are. -func test1(v: Int!) async throws -> [Int]! { nil } -func test1(v: Int!, other: String = "") throws -> [Int] { [] } - -func test2(v: Int!) -> [Any]! { nil } -func test2(v: Int!, other: String = "") throws -> [Int] { [] } - -func performTest(v: Int!) { - guard let _ = test1(v: v) as? [Int] else { // Ok - return - } - - guard let _ = test2(v: v) as? [Int] else { - // expected-error@-1 {{call can throw, but it is not marked with 'try' and the error is not handled}} - // expected-warning@-2 {{conditional cast from '[Int]' to '[Int]' always succeeds}} - return - } -} diff --git a/validation-test/Sema/type_checker_perf/fast/array_concatenation.swift b/validation-test/Sema/type_checker_perf/fast/array_concatenation.swift index 873889b4a20bd..1ce9d4293dc49 100644 --- a/validation-test/Sema/type_checker_perf/fast/array_concatenation.swift +++ b/validation-test/Sema/type_checker_perf/fast/array_concatenation.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift +// RUN: %target-typecheck-verify-swift -solver-disable-shrink // Self-contained test case protocol P1 {}; func f(_: T, _: T) -> T { fatalError() } diff --git a/validation-test/Sema/type_checker_perf/fast/array_count_property_vs_method.swift b/validation-test/Sema/type_checker_perf/fast/array_count_property_vs_method.swift deleted file mode 100644 index d4c0d407afa70..0000000000000 --- a/validation-test/Sema/type_checker_perf/fast/array_count_property_vs_method.swift +++ /dev/null @@ -1,8 +0,0 @@ -// RUN: %target-typecheck-verify-swift -solver-scope-threshold=11000 -// REQUIRES: tools-release,no_asan -// REQUIRES: OS=macosx - -func f(n: Int, a: [Int]) { - let _ = [(0 ..< n + a.count).map { Int8($0) }] + - [(0 ..< n + a.count).map { Int8($0) }.reversed()] // Ok -} diff --git a/validation-test/Sema/type_checker_perf/fast/array_literal_with_operators_and_double_literals.swift b/validation-test/Sema/type_checker_perf/fast/array_literal_with_operators_and_double_literals.swift deleted file mode 100644 index 49ce8b4d6abac..0000000000000 --- a/validation-test/Sema/type_checker_perf/fast/array_literal_with_operators_and_double_literals.swift +++ /dev/null @@ -1,31 +0,0 @@ -// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) %s -typecheck -solver-expression-time-threshold=1 - -// REQUIRES: asserts,no_asan -// REQUIRES: objc_interop - -// FIXME: This should be a scale-test but it doesn't allow passing `%clang-importer-sdk` - -// This import is important because it brings CGFloat and -// enables Double<->CGFloat implicit conversion that affects -// literals below. -import Foundation - -let p/*: [(String, Bool, Bool, Double)]*/ = [ - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0), - ("", true, true, 0 * 0.0 * 0.0) -] diff --git a/validation-test/Sema/type_checker_perf/fast/array_of_literals_and_operators_cast_to_float.swift.gyb b/validation-test/Sema/type_checker_perf/fast/array_of_literals_and_operators_cast_to_float.swift.gyb deleted file mode 100644 index 2cd48795d50c5..0000000000000 --- a/validation-test/Sema/type_checker_perf/fast/array_of_literals_and_operators_cast_to_float.swift.gyb +++ /dev/null @@ -1,10 +0,0 @@ -// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s -// REQUIRES: asserts,no_asan - -let _ = [ - 0, -%for i in range(2, N+2): - 1/${i}, -%end - 1 -] as [Float] diff --git a/validation-test/Sema/type_checker_perf/slow/borderline_flat_map_operator_mix.swift b/validation-test/Sema/type_checker_perf/fast/borderline_flat_map_operator_mix.swift similarity index 50% rename from validation-test/Sema/type_checker_perf/slow/borderline_flat_map_operator_mix.swift rename to validation-test/Sema/type_checker_perf/fast/borderline_flat_map_operator_mix.swift index b88a73d90121f..cca07954da499 100644 --- a/validation-test/Sema/type_checker_perf/slow/borderline_flat_map_operator_mix.swift +++ b/validation-test/Sema/type_checker_perf/fast/borderline_flat_map_operator_mix.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 +// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=10 // REQUIRES: no_asan @@ -10,11 +10,8 @@ struct S { } } -// Note: One possible approach to this issue would be to determine when the array literal inside of the inner closure -// doesn't have any other possible bindings but Array and attempt it at that point. That would fail overload of flatMap -// that returns an optional value. func f(x: Array, y: Range) -> [S] { - return x.flatMap { z in // expected-error {{the compiler is unable to type-check this expression in reasonable time}} + return x.flatMap { z in return ((y.lowerBound / 1)...(y.upperBound + 1) / 1).flatMap { w in return [S(1 * Double(w) + 1.0 + z.t), S(1 * Double(w) + 1.0 - z.t)] diff --git a/validation-test/Sema/type_checker_perf/fast/cgfloat_with_unary_operators.swift b/validation-test/Sema/type_checker_perf/fast/cgfloat_with_unary_operators.swift deleted file mode 100644 index ebf7490e9cfdf..0000000000000 --- a/validation-test/Sema/type_checker_perf/fast/cgfloat_with_unary_operators.swift +++ /dev/null @@ -1,19 +0,0 @@ -// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 - -// REQUIRES: OS=macosx,no_asan -// REQUIRES: objc_interop - -import Foundation -import CoreGraphics -import simd - -func test( - a: CGFloat, - b: CGFloat -) -> CGFloat { - exp(-a * b) * - (a * -sin(a * b) * a + ((a * b + a) / b) * cos(a * b) * a) + - exp(-a * b) * - (-b) * - (a * cos(a * b) + ((a * b + a) / b) * sin(a * b)) -} diff --git a/validation-test/Sema/type_checker_perf/fast/complex_swiftui_padding_conditions.swift b/validation-test/Sema/type_checker_perf/fast/complex_swiftui_padding_conditions.swift deleted file mode 100644 index df038a16ff4cc..0000000000000 --- a/validation-test/Sema/type_checker_perf/fast/complex_swiftui_padding_conditions.swift +++ /dev/null @@ -1,17 +0,0 @@ -// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx10.15 -swift-version 5 -// REQUIRES: OS=macosx - -import SwiftUI - -func test(a: [(offset: Int, element: Double)], - c: Color, - x: CGFloat, - n: Int -) -> some View { - ForEach(a, id: \.offset) { i, r in - RoundedRectangle(cornerRadius: r, style: .continuous) - .stroke(c, lineWidth: 1) - .padding(.horizontal, x / Double(n) * Double(n - 1 - i) / 2) - .padding(.vertical, x / Double(n) * Double(n - 1 - i) / 2) - } -} diff --git a/validation-test/Sema/type_checker_perf/fast/leading_dot_syntax_in_literal_array.swift.gyb b/validation-test/Sema/type_checker_perf/fast/leading_dot_syntax_in_literal_array.swift.gyb deleted file mode 100644 index a022b5c29a3d3..0000000000000 --- a/validation-test/Sema/type_checker_perf/fast/leading_dot_syntax_in_literal_array.swift.gyb +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: %scale-test --begin 1 --end 15 --step 1 --select NumLeafScopes %s --expected-exit-code 0 -// REQUIRES: asserts,no_asan - -enum E { - case a - case b - case c(Int32) -} - -struct Tester { - mutating func test(arr: [E], cond: Bool = false) {} - mutating func test(arr: E..., cond: Bool = false) {} -} - -func test() { - var tester = Tester() - tester.test(arr: [ - .c(1), .a, -%for i in range(N): - .c(1 << 4 | 8), .c(0), -%end - .c(1), .b]) -} diff --git a/validation-test/Sema/type_checker_perf/fast/member_chains_and_operators_with_iou_base_types.swift b/validation-test/Sema/type_checker_perf/fast/member_chains_and_operators_with_iou_base_types.swift deleted file mode 100644 index d00b81af40f38..0000000000000 --- a/validation-test/Sema/type_checker_perf/fast/member_chains_and_operators_with_iou_base_types.swift +++ /dev/null @@ -1,35 +0,0 @@ -// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) %s -typecheck -solver-expression-time-threshold=1 - -// REQUIRES: OS=macosx,no_asan -// REQUIRES: objc_interop - -import Foundation - -struct CGRect { - var x: CGFloat - - init(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) { } - init(x: Double, y: Double, width: Double, height: Double) { } -} - -protocol View {} - -extension Optional: View where Wrapped: View {} - -extension View { - func frame() -> some View { self } - func frame(x: Int, y: Int, w: Int, z: Int) -> some View { self } - func frame(y: Bool) -> some View { self } -} - -struct NSView { - var frame: CGRect -} - -func test(margin: CGFloat, view: NSView!) -> CGRect { - // `view` is first attempted as `NSView?` and only if that fails is force unwrapped - return CGRect(x: view.frame.x + margin, - y: view.frame.x + margin, - width: view.frame.x - view.frame.x - view.frame.x - (margin * 2), - height: margin) -} diff --git a/validation-test/Sema/type_checker_perf/fast/property_vs_unapplied_func.swift b/validation-test/Sema/type_checker_perf/fast/property_vs_unapplied_func.swift index 0f44ea3c31032..9cd53902f42ad 100644 --- a/validation-test/Sema/type_checker_perf/fast/property_vs_unapplied_func.swift +++ b/validation-test/Sema/type_checker_perf/fast/property_vs_unapplied_func.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 +// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 -solver-disable-shrink // REQUIRES: tools-release,no_asan struct Date { diff --git a/validation-test/Sema/type_checker_perf/fast/rdar133340307.swift b/validation-test/Sema/type_checker_perf/fast/rdar133340307.swift deleted file mode 100644 index 9740b4d15f06c..0000000000000 --- a/validation-test/Sema/type_checker_perf/fast/rdar133340307.swift +++ /dev/null @@ -1,134 +0,0 @@ -// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 -// REQUIRES: tools-release,no_asan - -public protocol Applicative {} - -public struct Kind {} - -public extension Applicative { - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } - - static func product (_ fa:Kind, _ fb:Kind) -> Kind { - fatalError() - } -} - -public extension Applicative { - static func zip (_ fa0:Kind, _ fa1:Kind, _ fa2:Kind, _ fa3:Kind, _ fa4:Kind, _ fa5:Kind, _ fa6:Kind, _ fa7:Kind, _ fa8:Kind, _ fa9:Kind, _ fa10:Kind, _ fa11:Kind, _ fa12:Kind, _ fa13:Kind, _ fa14:Kind, _ fa15:Kind, _ fa16:Kind, _ fa17:Kind, _ fa18:Kind, _ fa19:Kind, _ fa20:Kind, _ fa21:Kind, _ fa22:Kind, _ fa23:Kind, _ fa24:Kind, _ fa25:Kind, _ fa26:Kind, _ fa27:Kind, _ fa28:Kind, _ fa29:Kind) -> Kind { - product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(fa0, fa1), fa2), fa3), fa4), fa5), fa6), fa7), fa8), fa9), fa10), fa11), fa12), fa13), fa14), fa15), fa16), fa17), fa18), fa19), fa20), fa21), fa22), fa23), fa24), fa25), fa26), fa27), fa28), fa29) // Ok - } -} diff --git a/validation-test/Sema/type_checker_perf/slow/collections_chained_with_plus.swift.gyb b/validation-test/Sema/type_checker_perf/slow/collections_chained_with_plus.swift.gyb deleted file mode 100644 index 82e558a5a73fb..0000000000000 --- a/validation-test/Sema/type_checker_perf/slow/collections_chained_with_plus.swift.gyb +++ /dev/null @@ -1,18 +0,0 @@ -// RUN: %scale-test --invert-result --begin 1 --end 5 --step 1 --select NumLeafScopes %s -Xfrontend=-typecheck -// REQUIRES: asserts, no_asan - -struct Value: RandomAccessCollection, RangeReplaceableCollection { - let startIndex = 0 - let endIndex = 0 - - subscript(_: Int) -> Int { 0 } - - func replaceSubrange(_: Range, with: C) {} -} - -func f(v: Value) { - let _ = v -%for i in range(0, N): - + v -%end -} diff --git a/validation-test/Sema/type_checker_perf/fast/nil_coalescing.swift.gyb b/validation-test/Sema/type_checker_perf/slow/nil_coalescing.swift.gyb similarity index 58% rename from validation-test/Sema/type_checker_perf/fast/nil_coalescing.swift.gyb rename to validation-test/Sema/type_checker_perf/slow/nil_coalescing.swift.gyb index af8639d9bc159..2ef5c8f16a3d7 100644 --- a/validation-test/Sema/type_checker_perf/fast/nil_coalescing.swift.gyb +++ b/validation-test/Sema/type_checker_perf/slow/nil_coalescing.swift.gyb @@ -1,4 +1,4 @@ -// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s +// RUN: %scale-test --invert-result --begin 1 --end 5 --step 1 --select NumLeafScopes %s // REQUIRES: asserts,no_asan func t(_ x: Int?) -> Int { diff --git a/validation-test/Sema/type_checker_perf/fast/operator_chain_with_hetergeneous_arguments.swift b/validation-test/Sema/type_checker_perf/slow/operator_chain_with_hetergeneous_arguments.swift similarity index 90% rename from validation-test/Sema/type_checker_perf/fast/operator_chain_with_hetergeneous_arguments.swift rename to validation-test/Sema/type_checker_perf/slow/operator_chain_with_hetergeneous_arguments.swift index 787509fb565b2..5d6c8c4440ece 100644 --- a/validation-test/Sema/type_checker_perf/fast/operator_chain_with_hetergeneous_arguments.swift +++ b/validation-test/Sema/type_checker_perf/slow/operator_chain_with_hetergeneous_arguments.swift @@ -5,4 +5,5 @@ func test(bytes: Int, length: UInt32) { // left-hand side of `>=` is `Int` and right-hand side is a chain of `UInt32` inferred from `length` _ = bytes >= 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + length + // expected-error@-1 {{reasonable time}} } diff --git a/validation-test/Sema/type_checker_perf/fast/rdar17170728.swift b/validation-test/Sema/type_checker_perf/slow/rdar17170728.swift similarity index 74% rename from validation-test/Sema/type_checker_perf/fast/rdar17170728.swift rename to validation-test/Sema/type_checker_perf/slow/rdar17170728.swift index 1e64e4194e4b6..62c2bb2ea367b 100644 --- a/validation-test/Sema/type_checker_perf/fast/rdar17170728.swift +++ b/validation-test/Sema/type_checker_perf/slow/rdar17170728.swift @@ -5,6 +5,7 @@ let i: Int? = 1 let j: Int? let k: Int? = 2 +// expected-error@+1 {{the compiler is unable to type-check this expression in reasonable time}} let _ = [i, j, k].reduce(0 as Int?) { $0 != nil && $1 != nil ? $0! + $1! : ($0 != nil ? $0! : ($1 != nil ? $1! : nil)) } diff --git a/validation-test/Sema/type_checker_perf/fast/rdar22770433.swift b/validation-test/Sema/type_checker_perf/slow/rdar22770433.swift similarity index 74% rename from validation-test/Sema/type_checker_perf/fast/rdar22770433.swift rename to validation-test/Sema/type_checker_perf/slow/rdar22770433.swift index 0c51c9346d7ac..f063e685689de 100644 --- a/validation-test/Sema/type_checker_perf/fast/rdar22770433.swift +++ b/validation-test/Sema/type_checker_perf/slow/rdar22770433.swift @@ -2,6 +2,7 @@ // REQUIRES: tools-release,no_asan func test(n: Int) -> Int { + // expected-error@+1 {{the compiler is unable to type-check this expression in reasonable time}} return n == 0 ? 0 : (0.. 0 && $1 % 2 == 0) ? ((($0 + $1) - ($0 + $1)) / ($1 - $0)) + (($0 + $1) / ($1 - $0)) : $0 } diff --git a/validation-test/Sema/type_checker_perf/fast/rdar23682605.swift b/validation-test/Sema/type_checker_perf/slow/rdar23682605.swift similarity index 91% rename from validation-test/Sema/type_checker_perf/fast/rdar23682605.swift rename to validation-test/Sema/type_checker_perf/slow/rdar23682605.swift index 4be53b4d2ef83..b7bc757fe1989 100644 --- a/validation-test/Sema/type_checker_perf/fast/rdar23682605.swift +++ b/validation-test/Sema/type_checker_perf/slow/rdar23682605.swift @@ -14,6 +14,7 @@ func memoize( body: @escaping ((T)->U, T)->U ) -> (T)->U { } let fibonacci = memoize { + // expected-error@-1 {{reasonable time}} fibonacci, n in n < 2 ? Double(n) : fibonacci(n - 1) + fibonacci(n - 2) } diff --git a/validation-test/Sema/type_checker_perf/slow/rdar26564101.swift b/validation-test/Sema/type_checker_perf/slow/rdar26564101.swift index e9e1d32c90f05..09ca79329d7a3 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar26564101.swift +++ b/validation-test/Sema/type_checker_perf/slow/rdar26564101.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 +// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 -solver-disable-shrink // REQUIRES: tools-release,no_asan // UNSUPPORTED: swift_test_mode_optimize_none && OS=linux-gnu diff --git a/validation-test/Sema/type_checker_perf/fast/rdar31742586.swift b/validation-test/Sema/type_checker_perf/slow/rdar31742586.swift similarity index 67% rename from validation-test/Sema/type_checker_perf/fast/rdar31742586.swift rename to validation-test/Sema/type_checker_perf/slow/rdar31742586.swift index 2c694c570a137..0cc33ce8253cf 100644 --- a/validation-test/Sema/type_checker_perf/fast/rdar31742586.swift +++ b/validation-test/Sema/type_checker_perf/slow/rdar31742586.swift @@ -3,4 +3,5 @@ func rdar31742586() -> Double { return -(1 + 2) + -(3 + 4) + 5 - (-(1 + 2) + -(3 + 4) + 5) + // expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time}} } diff --git a/validation-test/Sema/type_checker_perf/fast/rdar35213699.swift b/validation-test/Sema/type_checker_perf/slow/rdar35213699.swift similarity index 66% rename from validation-test/Sema/type_checker_perf/fast/rdar35213699.swift rename to validation-test/Sema/type_checker_perf/slow/rdar35213699.swift index ea333f993b6a3..5d89ab1541a89 100644 --- a/validation-test/Sema/type_checker_perf/fast/rdar35213699.swift +++ b/validation-test/Sema/type_checker_perf/slow/rdar35213699.swift @@ -3,4 +3,6 @@ func test() { let _: UInt = 1 * 2 + 3 * 4 + 5 * 6 + 7 * 8 + 9 * 10 + 11 * 12 + 13 * 14 + // expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time}} } + diff --git a/validation-test/Sema/type_checker_perf/fast/rdar46713933_literal_arg.swift b/validation-test/Sema/type_checker_perf/slow/rdar46713933_literal_arg.swift similarity index 85% rename from validation-test/Sema/type_checker_perf/fast/rdar46713933_literal_arg.swift rename to validation-test/Sema/type_checker_perf/slow/rdar46713933_literal_arg.swift index 5256a92a787c7..a0628335b9c36 100644 --- a/validation-test/Sema/type_checker_perf/fast/rdar46713933_literal_arg.swift +++ b/validation-test/Sema/type_checker_perf/slow/rdar46713933_literal_arg.swift @@ -8,4 +8,5 @@ func wrap(_ key: String, _ value: T) -> T { retur func wrapped() -> Int { return wrap("1", 1) + wrap("1", 1) + wrap("1", 1) + wrap("1", 1) + wrap("1", 1) + wrap("1", 1) + // expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time}} } diff --git a/validation-test/Sema/type_checker_perf/fast/rdar91310777.swift b/validation-test/Sema/type_checker_perf/slow/rdar91310777.swift similarity index 66% rename from validation-test/Sema/type_checker_perf/fast/rdar91310777.swift rename to validation-test/Sema/type_checker_perf/slow/rdar91310777.swift index 18450b15fe401..eb9dcbee848c8 100644 --- a/validation-test/Sema/type_checker_perf/fast/rdar91310777.swift +++ b/validation-test/Sema/type_checker_perf/slow/rdar91310777.swift @@ -9,6 +9,7 @@ func test() { compute { print(x) let v: UInt64 = UInt64((24 / UInt32(1)) + UInt32(0) - UInt32(0) - 24 / 42 - 42) + // expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions}} print(v) } } diff --git a/validation-test/Sema/type_checker_perf/fast/simd_scalar_multiple.swift.gyb b/validation-test/Sema/type_checker_perf/slow/simd_scalar_multiple.swift.gyb similarity index 70% rename from validation-test/Sema/type_checker_perf/fast/simd_scalar_multiple.swift.gyb rename to validation-test/Sema/type_checker_perf/slow/simd_scalar_multiple.swift.gyb index 1d5e4f9e0ea6f..497afd10785f5 100644 --- a/validation-test/Sema/type_checker_perf/fast/simd_scalar_multiple.swift.gyb +++ b/validation-test/Sema/type_checker_perf/slow/simd_scalar_multiple.swift.gyb @@ -1,4 +1,4 @@ -// RUN: %scale-test --begin 1 --end 5 --step 1 --select NumLeafScopes %s +// RUN: %scale-test --invert-result --begin 1 --end 5 --step 1 --select NumLeafScopes %s // REQUIRES: asserts,no_asan diff --git a/validation-test/Sema/type_checker_perf/fast/swift_package_index_1.swift b/validation-test/Sema/type_checker_perf/slow/swift_package_index_1.swift similarity index 77% rename from validation-test/Sema/type_checker_perf/fast/swift_package_index_1.swift rename to validation-test/Sema/type_checker_perf/slow/swift_package_index_1.swift index f02ec4fe258ca..934a80472f9c9 100644 --- a/validation-test/Sema/type_checker_perf/fast/swift_package_index_1.swift +++ b/validation-test/Sema/type_checker_perf/slow/swift_package_index_1.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=500 +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=1000 // REQUIRES: tools-release,no_asan public class Cookie { @@ -13,7 +13,7 @@ public class Cookie { private let fixedByteSize: Int32 = 56 var totalByteCount: Int32 { - return fixedByteSize + + return fixedByteSize + // expected-error {{the compiler is unable to type-check this expression in reasonable time}} (port != nil ? 2 : 0) + Int32(comment?.utf8.count ?? 0) + Int32(commentURL?.utf8.count ?? 0) + diff --git a/validation-test/Sema/type_checker_perf/fast/swift_package_index_2.swift.gyb b/validation-test/Sema/type_checker_perf/slow/swift_package_index_2.swift.gyb similarity index 90% rename from validation-test/Sema/type_checker_perf/fast/swift_package_index_2.swift.gyb rename to validation-test/Sema/type_checker_perf/slow/swift_package_index_2.swift.gyb index 258a967c38021..08f3063bd26d6 100644 --- a/validation-test/Sema/type_checker_perf/fast/swift_package_index_2.swift.gyb +++ b/validation-test/Sema/type_checker_perf/slow/swift_package_index_2.swift.gyb @@ -1,6 +1,8 @@ // RUN: %scale-test --begin 1 --end 10 --step 1 --select NumConstraintScopes %s // REQUIRES: tools-release,no_asan,asserts +// REQUIRES: rdar135382075 + var v: [String] = [] var vv: [String]? = nil diff --git a/validation-test/Sema/type_checker_perf/fast/swift_package_index_3.swift b/validation-test/Sema/type_checker_perf/slow/swift_package_index_3.swift similarity index 75% rename from validation-test/Sema/type_checker_perf/fast/swift_package_index_3.swift rename to validation-test/Sema/type_checker_perf/slow/swift_package_index_3.swift index 25afd5b754967..2327507262418 100644 --- a/validation-test/Sema/type_checker_perf/fast/swift_package_index_3.swift +++ b/validation-test/Sema/type_checker_perf/slow/swift_package_index_3.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=50000 +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=50000 // REQUIRES: tools-release,no_asan extension String { @@ -9,7 +9,7 @@ extension String { func getProperties( from ics: String ) -> [(name: String, value: String)] { - return ics + return ics // expected-error {{the compiler is unable to type-check this expression in reasonable time}} .replacingOccurrences(of: "\r\n ", with: "") .components(separatedBy: "\r\n") .map { $0.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: true) } diff --git a/validation-test/Sema/type_checker_perf/fast/swift_package_index_4.swift b/validation-test/Sema/type_checker_perf/slow/swift_package_index_4.swift similarity index 94% rename from validation-test/Sema/type_checker_perf/fast/swift_package_index_4.swift rename to validation-test/Sema/type_checker_perf/slow/swift_package_index_4.swift index eaff8718abba3..8b05bedbea950 100644 --- a/validation-test/Sema/type_checker_perf/fast/swift_package_index_4.swift +++ b/validation-test/Sema/type_checker_perf/slow/swift_package_index_4.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=60000 +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=60000 // REQUIRES: tools-release,no_asan protocol ArgumentProtocol {} @@ -47,7 +47,7 @@ struct Options { static func evaluate(_ mode: CommandMode) -> Result { let defaultBuildDirectory = "" - return create + return create // expected-error {{the compiler is unable to type-check this expression in reasonable time}} <*> mode <| Option(key: "", defaultValue: nil, usage: "") <*> mode <| Option(key: "", defaultValue: nil, usage: "") <*> mode <| Option(key: "", defaultValue: FileManager.default.currentDirectoryPath, usage: "") diff --git a/validation-test/Sema/type_checker_perf/fast/swift_package_index_5.swift b/validation-test/Sema/type_checker_perf/slow/swift_package_index_5.swift similarity index 54% rename from validation-test/Sema/type_checker_perf/fast/swift_package_index_5.swift rename to validation-test/Sema/type_checker_perf/slow/swift_package_index_5.swift index e05dda77878eb..e20ea384f9a5f 100644 --- a/validation-test/Sema/type_checker_perf/fast/swift_package_index_5.swift +++ b/validation-test/Sema/type_checker_perf/slow/swift_package_index_5.swift @@ -1,8 +1,8 @@ -// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=1000 -swift-version 5 +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=1000 -swift-version 5 // REQUIRES: tools-release,no_asan func g(_: T) throws { - let _: T? = + let _: T? = //expected-error {{the compiler is unable to type-check this expression in reasonable time}} (try? f() as? T) ?? (try? f() as? T) ?? (try? f() as? T) ?? diff --git a/validation-test/stdlib/FixedPointDiagnostics.swift.gyb b/validation-test/stdlib/FixedPointDiagnostics.swift.gyb index 2b2df2812c0d2..e091805af22b7 100644 --- a/validation-test/stdlib/FixedPointDiagnostics.swift.gyb +++ b/validation-test/stdlib/FixedPointDiagnostics.swift.gyb @@ -53,8 +53,8 @@ func testMixedSignArithmetic() { Stride(1) - ${T}(0) // expected-error {{}} expected-note {{}} var y: Stride = 0 - y += ${T}(1) // expected-error {{cannot convert value of type '${T}' to expected argument type 'Stride' (aka 'Int')}} {{10-10=Stride(}} - y -= ${T}(1) // expected-error {{cannot convert value of type '${T}' to expected argument type 'Stride' (aka 'Int')}} {{10-10=Stride(}} + y += ${T}(1) // expected-error {{cannot convert value of type '${T}' to expected argument type 'Int'}} {{10-10=Int(}} + y -= ${T}(1) // expected-error {{cannot convert value of type '${T}' to expected argument type 'Int'}} {{10-10=Int(}} } % end }