diff --git a/include/swift/AST/KnownStdlibTypes.def b/include/swift/AST/KnownStdlibTypes.def index 425c846e26854..77b9d5b84e463 100644 --- a/include/swift/AST/KnownStdlibTypes.def +++ b/include/swift/AST/KnownStdlibTypes.def @@ -70,6 +70,7 @@ KNOWN_STDLIB_TYPE_DECL(WritableKeyPath, NominalTypeDecl, 2) KNOWN_STDLIB_TYPE_DECL(ReferenceWritableKeyPath, NominalTypeDecl, 2) KNOWN_STDLIB_TYPE_DECL(Optional, EnumDecl, 1) +KNOWN_STDLIB_TYPE_DECL(_OptionalNilComparisonType, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(OptionSet, NominalTypeDecl, 1) diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index e829c02d0045e..d6a9e36ecbf8b 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -983,9 +983,6 @@ 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 5ecb570a1911a..7d1cc7aa8dc53 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -856,10 +856,6 @@ 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/CSBindings.h b/include/swift/Sema/CSBindings.h index e67ad8602ee8b..b47bb84294f2e 100644 --- a/include/swift/Sema/CSBindings.h +++ b/include/swift/Sema/CSBindings.h @@ -527,6 +527,12 @@ class BindingSet { void forEachLiteralRequirement( llvm::function_ref callback) const; + void forEachAdjacentVariable( + llvm::function_ref callback) const { + for (auto *typeVar : AdjacentVars) + callback(typeVar); + } + /// Return a literal requirement that has the most impact on the binding /// score. LiteralBindingKind getLiteralForScore() const; diff --git a/include/swift/Sema/Constraint.h b/include/swift/Sema/Constraint.h index 72db2f161ba39..9d7195f168366 100644 --- a/include/swift/Sema/Constraint.h +++ b/include/swift/Sema/Constraint.h @@ -852,11 +852,6 @@ 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 4255553304fd2..2ea0e230ceb98 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -486,6 +486,18 @@ 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; + + /// Determine whether this type variable represents a type of the ternary + /// operator. + bool isTernary() const; + /// Retrieve the representative of the equivalence class to which this /// type variable belongs. /// @@ -2258,10 +2270,6 @@ 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. /// @@ -2472,74 +2480,6 @@ 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, @@ -3063,15 +3003,6 @@ 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. /// @@ -5374,19 +5305,11 @@ 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. - Constraint *selectDisjunction(); + /// \returns The selected disjunction and a set of it's favored choices. + std::optional>> + selectDisjunction(); /// Pick a conjunction from the InactiveConstraints list. /// @@ -5575,11 +5498,6 @@ 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 @@ -6086,6 +6004,12 @@ class DisjunctionChoice { return false; } + bool isDisfavored() const { + if (auto *decl = getOverloadChoiceDecl(Choice)) + return decl->getAttrs().hasAttribute(); + return false; + } + bool isBeginningOfPartition() const { return IsBeginningOfPartition; } // FIXME: All three of the accessors below are required to support @@ -6320,7 +6244,8 @@ class DisjunctionChoiceProducer : public BindingProducer { public: using Element = DisjunctionChoice; - DisjunctionChoiceProducer(ConstraintSystem &cs, Constraint *disjunction) + DisjunctionChoiceProducer(ConstraintSystem &cs, Constraint *disjunction, + llvm::TinyPtrVector &favorites) : BindingProducer(cs, disjunction->shouldRememberChoice() ? disjunction->getLocator() : nullptr), @@ -6330,6 +6255,11 @@ 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); } @@ -6374,8 +6304,9 @@ 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 3538ab0de8d59..5178796b2b935 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -2037,9 +2037,6 @@ 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 84ae48ce0f610..96d8839268e29 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -14,6 +14,7 @@ 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 807cdec5f30fc..54ad8b4a191d6 100644 --- a/lib/Sema/CSBindings.cpp +++ b/lib/Sema/CSBindings.cpp @@ -31,7 +31,6 @@ using namespace swift; using namespace constraints; using namespace inference; - void ConstraintGraphNode::initBindingSet() { ASSERT(!hasBindingSet()); ASSERT(forRepresentativeVar()); @@ -39,6 +38,12 @@ 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); @@ -1348,7 +1353,31 @@ bool BindingSet::isViable(PotentialBinding &binding, bool isTransitive) { if (!existingNTD || NTD != existingNTD) continue; - // FIXME: What is going on here needs to be thoroughly re-evaluated. + // 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; + } + } // 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 c19ec3a0a28b5..d271ec2ff2b02 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -43,10 +43,6 @@ 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) { @@ -77,698 +73,6 @@ 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 (isa(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().gatherNearbyConstraints( - argTypeVar, - [](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. @@ -799,62 +103,6 @@ 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) { @@ -1051,6 +299,61 @@ namespace { return tv; } + /// Attempt to infer a result type of a subscript reference where + /// the base type is either a stdlib Array or a Dictionary type. + /// This is a more principled version of the old performance hack + /// that used "favored" types deduced by the constraint optimizer + /// and is important to maintain pre-existing solver behavior. + Type inferCollectionSubscriptResultType(Type baseTy, + ArgumentList *argumentList) { + auto isLValueBase = false; + auto baseObjTy = baseTy; + if (baseObjTy->is()) { + isLValueBase = true; + baseObjTy = baseObjTy->getWithoutSpecifierType(); + } + + auto subscriptResultType = [&isLValueBase](Type valueTy, + bool isOptional) -> Type { + Type outputTy = isOptional ? OptionalType::get(valueTy) : valueTy; + return isLValueBase ? LValueType::get(outputTy) : outputTy; + }; + + if (auto *argument = argumentList->getUnlabeledUnaryExpr()) { + auto argumentTy = CS.getType(argument); + + auto elementTy = baseObjTy->getArrayElementType(); + + if (!elementTy) + elementTy = baseObjTy->getInlineArrayElementType(); + + if (elementTy) { + if (auto arraySliceTy = + dyn_cast(baseObjTy.getPointer())) { + baseObjTy = arraySliceTy->getDesugaredType(); + } + + if (argumentTy->isInt() || isExpr(argument)) + return subscriptResultType(elementTy, /*isOptional*/ false); + } else if (auto dictTy = CS.isDictionaryType(baseObjTy)) { + auto [keyTy, valueTy] = *dictTy; + + if (keyTy->isString() && + (isExpr(argument) || + isExpr(argument))) + return subscriptResultType(valueTy, /*isOptional*/ true); + + if (keyTy->isInt() && isExpr(argument)) + return subscriptResultType(valueTy, /*isOptional*/ true); + + if (keyTy->isEqual(argumentTy)) + return subscriptResultType(valueTy, /*isOptional*/ true); + } + } + + return Type(); + } + /// Add constraints for a subscript operation. Type addSubscriptConstraints( Expr *anchor, Type baseTy, ValueDecl *declOrNull, ArgumentList *argList, @@ -1074,52 +377,10 @@ namespace { Type outputTy; - // For an integer subscript expression on an array slice type, instead of - // introducing a new type variable we can easily obtain the element type. - if (isa(anchor)) { - - auto isLValueBase = false; - auto baseObjTy = baseTy; - if (baseObjTy->is()) { - isLValueBase = true; - baseObjTy = baseObjTy->getWithoutSpecifierType(); - } - - auto elementTy = baseObjTy->getArrayElementType(); - - if (!elementTy) - elementTy = baseObjTy->getInlineArrayElementType(); - - if (elementTy) { - - if (auto arraySliceTy = - dyn_cast(baseObjTy.getPointer())) { - baseObjTy = arraySliceTy->getDesugaredType(); - } - - if (argList->isUnlabeledUnary() && - isa(argList->getExpr(0))) { + // Attempt to infer the result type of a stdlib collection subscript. + if (isa(anchor)) + outputTy = inferCollectionSubscriptResultType(baseTy, argList); - outputTy = elementTy; - - 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); - } - } - } - } - if (outputTy.isNull()) { outputTy = CS.createTypeVariable(resultLocator, TVO_CanBindToLValue | TVO_CanBindToNoEscape); @@ -1171,7 +432,6 @@ namespace { Type fixedOutputType = CS.getFixedTypeRecursive(outputTy, /*wantRValue=*/false); if (!fixedOutputType->isTypeVariableOrMember()) { - CS.setFavoredType(anchor, fixedOutputType.getPointer()); outputTy = fixedOutputType; } @@ -1613,11 +873,6 @@ namespace { getParentPackExpansionExpr(E)); } } - - if (!knownType->hasPlaceholder()) { - // Set the favored type for this expression to the known type. - CS.setFavoredType(E, knownType.getPointer()); - } } } @@ -2089,9 +1344,6 @@ namespace { CS.getASTContext()); } - if (auto favoredTy = CS.getFavoredType(expr->getSubExpr())) { - CS.setFavoredType(expr, favoredTy); - } return CS.getType(expr->getSubExpr()); } @@ -3380,7 +2632,6 @@ namespace { Type fixedType = CS.getFixedTypeRecursive(resultType, /*wantRvalue=*/true); if (!fixedType->isTypeVariableOrMember()) { - CS.setFavoredType(expr, fixedType.getPointer()); resultType = fixedType; } @@ -4435,12 +3686,7 @@ static Expr *generateConstraintsFor(ConstraintSystem &cs, Expr *expr, ConstraintGenerator cg(cs, DC); ConstraintWalker cw(cg); - Expr *result = expr->walk(cw); - - if (result) - cs.optimizeConstraints(result); - - return result; + return expr->walk(cw); } bool ConstraintSystem::generateWrappedPropertyTypeConstraints( @@ -5174,26 +4420,6 @@ 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 new file mode 100644 index 0000000000000..ed9ebeef3c856 --- /dev/null +++ b/lib/Sema/CSOptimizer.cpp @@ -0,0 +1,1915 @@ +//===--- 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/Defer.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/PointerIntPair.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; + + /// Whether the decisions were based on speculative information + /// i.e. literal argument candidates or initializer type inference. + bool IsSpeculative; + + DisjunctionInfo() = default; + DisjunctionInfo(std::optional score, + ArrayRef favoredChoices, bool speculative) + : Score(score), FavoredChoices(favoredChoices), + IsSpeculative(speculative) {} + + static DisjunctionInfo none() { return {std::nullopt, {}, false}; } +}; + +class DisjunctionInfoBuilder { + std::optional Score; + SmallVector FavoredChoices; + bool IsSpeculative; + +public: + DisjunctionInfoBuilder(std::optional score) + : DisjunctionInfoBuilder(score, {}) {} + + DisjunctionInfoBuilder(std::optional score, + ArrayRef favoredChoices) + : Score(score), + FavoredChoices(favoredChoices.begin(), favoredChoices.end()), + IsSpeculative(false) {} + + void setFavoredChoices(ArrayRef choices) { + FavoredChoices.clear(); + FavoredChoices.append(choices.begin(), choices.end()); + } + + void addFavoredChoice(Constraint *constraint) { + FavoredChoices.push_back(constraint); + } + + void setSpeculative(bool value = true) { IsSpeculative = value; } + + DisjunctionInfo build() { return {Score, FavoredChoices, IsSpeculative}; } +}; + +static DeclContext *getDisjunctionDC(Constraint *disjunction) { + auto *choice = disjunction->getNestedConstraints()[0]; + switch (choice->getKind()) { + case ConstraintKind::BindOverload: + case ConstraintKind::ValueMember: + case ConstraintKind::UnresolvedValueMember: + case ConstraintKind::ValueWitness: + return choice->getDeclContext(); + default: + return nullptr; + } +} + +/// Determine whether the given disjunction appears in a context +/// transformed by a result builder. +static bool isInResultBuilderContext(ConstraintSystem &cs, + Constraint *disjunction) { + auto *DC = getDisjunctionDC(disjunction); + if (!DC) + return false; + + do { + auto fnContext = AnyFunctionRef::fromDeclContext(DC); + if (!fnContext) + return false; + + if (cs.getAppliedResultBuilderTransform(*fnContext)) + return true; + + } while ((DC = DC->getParent())); + + return false; +} + +/// If the given operator disjunction appears in some position +// inside of a not yet resolved call i.e. `a.b(1 + c(4) - 1)` +// both `+` and `-` are "in" argument context of `b`. +static bool isOperatorPassedToUnresolvedCall(ConstraintSystem &cs, + Constraint *disjunction) { + ASSERT(isOperatorDisjunction(disjunction)); + + auto *curr = castToExpr(disjunction->getLocator()->getAnchor()); + while (auto *parent = cs.getParentExpr(curr)) { + SWIFT_DEFER { curr = parent; }; + + switch (parent->getKind()) { + case ExprKind::OptionalEvaluation: + case ExprKind::Paren: + case ExprKind::Binary: + case ExprKind::PrefixUnary: + case ExprKind::PostfixUnary: + continue; + + // a.b(<> ? <> : <<...>>) + case ExprKind::Ternary: { + auto *T = cast(parent); + // If the operator is located in the condition it's + // not tied to the context. + if (T->getCondExpr() == curr) + return false; + + // But the branches are connected to the context. + continue; + } + + // Handles `a(<>), `a[<>]`, + // `.a(<>)` etc. + case ExprKind::Call: { + auto *call = cast(parent); + + // Type(...) + if (isa(call->getFn())) { + auto *ctorLoc = cs.getConstraintLocator( + call, {LocatorPathElt::ApplyFunction(), + LocatorPathElt::ConstructorMember()}); + return !cs.findSelectedOverloadFor(ctorLoc); + } + + // Ignore injected result builder methods like `buildExpression` + // and `buildBlock`. + if (auto *UDE = dyn_cast(call->getFn())) { + if (isResultBuilderMethodReference(cs.getASTContext(), UDE)) + return false; + } + + return !cs.findSelectedOverloadFor(call->getFn()); + } + + default: + return false; + } + } + + return false; +} + +// 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 isUnboundDictionaryType(Type type) { + if (auto *UGT = type->getAs()) + return UGT->getDecl() == type->getASTContext().getDictionaryDecl(); + 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() || name.isNilCoalescingOperator()) { + 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(Constraint *disjunction) { + auto *choice = disjunction->getNestedConstraints()[0]; + if (auto *decl = getOverloadChoiceDecl(choice)) + return decl->isOperator() && + decl->getBaseIdentifier().isStandardComparisonOperator(); + return false; +} + +static bool isOperatorNamed(Constraint *disjunction, StringRef name) { + auto *choice = disjunction->getNestedConstraints()[0]; + if (auto *decl = getOverloadChoiceDecl(choice)) + return decl->isOperator() && decl->getBaseIdentifier().is(name); + return false; +} + +static bool isArithmeticOperator(ValueDecl *decl) { + return decl->isOperator() && decl->getBaseIdentifier().isArithmeticOperator(); +} + +/// Generic choices are supported only if they are not complex enough +/// that would they'd require solving to figure out whether they are a +/// potential match or not. +static bool isSupportedGenericOverloadChoice(ValueDecl *decl, + GenericFunctionType *choiceType) { + // Same type requirements cannot be handled because each + // candidate-parameter pair is (currently) considered in isolation. + if (llvm::any_of(choiceType->getRequirements(), [](const Requirement &req) { + switch (req.getKind()) { + case RequirementKind::SameType: + case RequirementKind::SameShape: + return true; + + case RequirementKind::Conformance: + case RequirementKind::Superclass: + case RequirementKind::Layout: + return false; + } + })) + return false; + + // If there are no same-type requirements, allow signatures + // that use only concrete types or generic parameters directly + // in their parameter positions i.e. `(T, Int)`. + + auto *paramList = decl->getParameterList(); + if (!paramList) + return false; + + return llvm::all_of(paramList->getArray(), [](const ParamDecl *P) { + auto paramType = P->getInterfaceType(); + return paramType->is() || + !paramType->hasTypeParameter(); + }); +} + +static bool isSupportedDisjunction(Constraint *disjunction) { + auto choices = disjunction->getNestedConstraints(); + + if (isOperatorDisjunction(disjunction)) + return isSupportedOperator(disjunction); + + 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 complex 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; + + auto choiceType = decl->getInterfaceType()->getAs(); + if (!choiceType || choiceType->hasError()) + return false; + + // Non-generic choices are always supported. + if (choiceType->is()) + return true; + + if (auto *genericFn = choiceType->getAs()) + return isSupportedGenericOverloadChoice(decl, genericFn); + + return false; + } + + return false; + }); +} + +/// Determine whether the given overload choice constitutes a +/// valid choice that would be attempted during normal solving +/// without any score increases. +static ValueDecl *isViableOverloadChoice(ConstraintSystem &cs, + Constraint *constraint, + ConstraintLocator *locator) { + if (constraint->isDisabled()) + return nullptr; + + if (constraint->getKind() != ConstraintKind::BindOverload) + return nullptr; + + auto choice = constraint->getOverloadChoice(); + auto *decl = choice.getDeclOrNull(); + if (!decl) + return nullptr; + + // 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->getDeclContext()) { + if (!useDC->isDeclImported(decl)) + return nullptr; + } + } + + // If disjunction choice is unavailable we cannot + // do anything with it. + if (cs.isDeclUnavailable(decl, locator)) + return nullptr; + + return decl; +} + +/// Given the type variable that represents a result type of a +/// function call, check whether that call is to an initializer +/// and based on that deduce possible type for the result. +/// +/// @return A type and a flag that indicates whether there +/// are any viable failable overloads and empty pair if the +/// type variable isn't a result of an initializer call. +static llvm::PointerIntPair +inferTypeFromInitializerResultType(ConstraintSystem &cs, + TypeVariableType *typeVar, + ArrayRef disjunctions) { + assert(typeVar->getImpl().isFunctionResult()); + + auto *resultLoc = typeVar->getImpl().getLocator(); + auto *call = getAsExpr(resultLoc->getAnchor()); + if (!call) + return {}; + + auto *fn = call->getFn()->getSemanticsProvidingExpr(); + + Type instanceTy; + ConstraintLocator *ctorLocator = nullptr; + if (auto *typeExpr = getAsExpr(fn)) { + instanceTy = cs.getType(typeExpr)->getMetatypeInstanceType(); + ctorLocator = + cs.getConstraintLocator(call, {LocatorPathElt::ApplyFunction(), + LocatorPathElt::ConstructorMember()}); + } else if (auto *UDE = getAsExpr(fn)) { + if (!UDE->getName().getBaseName().isConstructor()) + return {}; + instanceTy = cs.getType(UDE->getBase())->getMetatypeInstanceType(); + ctorLocator = cs.getConstraintLocator(UDE, LocatorPathElt::Member()); + } + + if (!instanceTy || !ctorLocator) + return {}; + + auto initRef = + llvm::find_if(disjunctions, [&ctorLocator](Constraint *disjunction) { + return disjunction->getLocator() == ctorLocator; + }); + + if (initRef == disjunctions.end()) + return {}; + + unsigned numFailable = 0; + unsigned total = 0; + for (auto *choice : (*initRef)->getNestedConstraints()) { + auto *decl = isViableOverloadChoice(cs, choice, ctorLocator); + if (!decl || !isa(decl)) + continue; + + auto *ctor = cast(decl); + if (ctor->isFailable()) + ++numFailable; + + ++total; + } + + if (numFailable > 0) { + // If all of the active choices are failable, produce an optional + // type only. + if (numFailable == total) + return {instanceTy->wrapInOptionalType(), /*hasFailable=*/false}; + // Otherwise there are two options. + return {instanceTy, /*hasFailable*/ true}; + } + + return {instanceTy, /*hasFailable=*/false}; +} + +/// If the given expression represents a chain of operators that have +/// only declaration/member references and/or literals as arguments, +/// attempt to deduce a potential type of the chain. For example if +/// chain has only integral literals it's going to be `Int`, if there +/// are some floating-point literals mixed in - it's going to be `Double`. +static Type inferTypeOfArithmeticOperatorChain(ConstraintSystem &cs, + ASTNode node) { + class OperatorChainAnalyzer : public ASTWalker { + ASTContext &C; + DeclContext *DC; + ConstraintSystem &CS; + + llvm::SmallPtrSet, 2> candidates; + + bool unsupported = false; + + PreWalkResult walkToExprPre(Expr *expr) override { + if (isa(expr)) + return Action::Continue(expr); + + if (isa(expr) || isa(expr)) + return Action::Continue(expr); + + if (isa(expr)) + return Action::Continue(expr); + + // This inference works only with arithmetic operators + // because we know the structure of their overloads. + if (auto *ODRE = dyn_cast(expr)) { + if (auto *choice = ODRE->getDecls().front()) { + if (choice->getBaseIdentifier().isArithmeticOperator()) + return Action::Continue(expr); + } + } + + if (auto *LE = dyn_cast(expr)) { + if (auto *P = TypeChecker::getLiteralProtocol(C, LE)) { + if (auto defaultTy = TypeChecker::getDefaultType(P, DC)) { + addCandidateType(defaultTy, /*literal=*/true); + // String interpolation expressions have `TapExpr` + // as their children, no reason to walk them. + return Action::SkipChildren(expr); + } + } + } + + if (auto *UDE = dyn_cast(expr)) { + auto memberTy = CS.getType(UDE); + if (!memberTy->hasTypeVariable()) { + addCandidateType(memberTy, /*literal=*/false); + return Action::SkipChildren(expr); + } + } + + if (auto *DRE = dyn_cast(expr)) { + auto declTy = CS.getType(DRE); + if (!declTy->hasTypeVariable()) { + addCandidateType(declTy, /*literal=*/false); + return Action::SkipChildren(expr); + } + } + + unsupported = true; + return Action::Stop(); + } + + void addCandidateType(Type type, bool literal) { + if (literal) { + if (type->isInt()) { + // Floating-point types always subsume Int in operator chains. + if (llvm::any_of(candidates, [](const auto &candidate) { + auto ty = candidate.getPointer(); + return isFloatType(ty) || ty->isCGFloat(); + })) + return; + } else if (isFloatType(type) || type->isCGFloat()) { + // A single use of a floating-point literal flips the + // type of the entire chain to it. + (void)candidates.erase({C.getIntType(), /*literal=*/true}); + } + } + + candidates.insert({type, literal}); + } + + public: + OperatorChainAnalyzer(ConstraintSystem &CS) + : C(CS.getASTContext()), DC(CS.DC), CS(CS) {} + + Type chainType() const { + if (unsupported) + return Type(); + return candidates.size() != 1 ? Type() + : (*candidates.begin()).getPointer(); + } + }; + + OperatorChainAnalyzer analyzer(cs); + node.walk(analyzer); + + return analyzer.chainType(); +} + +NullablePtr getApplicableFnConstraint(ConstraintGraph &CG, + Constraint *disjunction) { + auto *boundVar = disjunction->getNestedConstraints()[0] + ->getFirstType() + ->getAs(); + if (!boundVar) + return nullptr; + + auto constraints = + CG.gatherNearbyConstraints(boundVar, [](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()) { + auto *decl = + isViableOverloadChoice(cs, constraint, disjunction->getLocator()); + if (!decl) + continue; + + Type overloadType = cs.getEffectiveOverloadType( + disjunction->getLocator(), constraint->getOverloadChoice(), + /*allowMembers=*/true, constraint->getDeclContext()); + + 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 = choice->getParameterList()) + return arguments->size() == paramList->size(); + return false; + }); +} + +static bool 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(); + }); +} + +/// 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; + + bool hasVariadicGenerics = false; + SmallVector favored; + + forEachDisjunctionChoice( + cs, disjunction, + [&](Constraint *choice, ValueDecl *decl, FunctionType *overloadType) { + if (decl->getAttrs().hasAttribute()) + return; + + if (isVariadicGenericOverload(decl)) { + hasVariadicGenerics = true; + return; + } + + if (overloadType->getNumParams() == argumentList->size() || + llvm::count_if(*decl->getParameterList(), [](auto *param) { + return !param->isDefaultArgument(); + }) == argumentList->size()) + favored.push_back(choice); + }); + + if (hasVariadicGenerics) + return; + + for (auto *choice : favored) + favoredChoice(choice); +} + +/// Preserves old behavior where, for unary calls, the solver would not previously +/// consider choices that didn't match on the number of parameters (regardless of +/// defaults and variadics) and only exact matches were favored. +static std::optional preserveFavoringOfUnlabeledUnaryArgument( + ConstraintSystem &cs, Constraint *disjunction, ArgumentList *argumentList) { + if (!argumentList->isUnlabeledUnary()) + return std::nullopt; + + if (!isExpr( + cs.getParentExpr(argumentList->getUnlabeledUnaryExpr()))) + return std::nullopt; + + // The hack rolled back favoring choices if one of the overloads was a + // protocol requirement or variadic generic. + // + // Note that it doesn't matter whether such overload choices are viable + // or not, their presence disabled this "optimization". + if (llvm::any_of(disjunction->getNestedConstraints(), [](Constraint *choice) { + auto *decl = getOverloadChoiceDecl(choice); + if (!decl) + return false; + + return isa(decl->getDeclContext()) || + (!decl->getAttrs().hasAttribute() && + isVariadicGenericOverload(decl)); + })) + return std::nullopt; + + auto ODRE = isOverloadedDeclRef(disjunction); + bool preserveFavoringOfUnlabeledUnaryArgument = + !ODRE || numOverloadChoicesMatchingOnArity(ODRE, argumentList) < 2; + + if (!preserveFavoringOfUnlabeledUnaryArgument) + return std::nullopt; + + auto *argument = + argumentList->getUnlabeledUnaryExpr()->getSemanticsProvidingExpr(); + // The hack operated on "favored" types and only declaration references, + // applications, and (dynamic) subscripts had them if they managed to + // get an overload choice selected during constraint generation. + // It's sometimes possible to infer a type of a literal and an operator + // chain, so it should be allowed as well. + if (!(isExpr(argument) || isExpr(argument) || + isExpr(argument) || + isExpr(argument) || + isExpr(argument) || isExpr(argument))) + return DisjunctionInfo::none(); + + auto argumentType = cs.getType(argument)->getRValueType(); + + // For chains like `1 + 2 * 3` it's easy to deduce the type because + // we know what literal types are preferred. + if (isa(argument)) { + auto chainTy = inferTypeOfArithmeticOperatorChain(cs, argument); + if (!chainTy) + return DisjunctionInfo::none(); + + argumentType = chainTy; + } + + // Use default type of a literal (when available) to make a guess. + // This is what old hack used to do as well. + if (auto *LE = dyn_cast(argument)) { + auto *P = TypeChecker::getLiteralProtocol(cs.getASTContext(), LE); + if (!P) + return DisjunctionInfo::none(); + + auto defaultTy = TypeChecker::getDefaultType(P, cs.DC); + if (!defaultTy) + return DisjunctionInfo::none(); + + argumentType = defaultTy; + } + + ASSERT(argumentType); + + if (argumentType->hasTypeVariable() || argumentType->hasDependentMember()) + return DisjunctionInfo::none(); + + SmallVector favoredChoices; + forEachDisjunctionChoice( + cs, disjunction, + [&argumentType, &favoredChoices, &argument]( + Constraint *choice, ValueDecl *decl, FunctionType *overloadType) { + if (decl->getAttrs().hasAttribute()) + return; + + if (overloadType->getNumParams() != 1) + return; + + auto ¶m = overloadType->getParams()[0]; + + // Literals are speculative, let's not attempt to apply them too + // eagerly. + if (!param.getParameterFlags().isNone() && + (isa(argument) || isa(argument))) + return; + + if (argumentType->isEqual(param.getPlainType())) + favoredChoices.push_back(choice); + }); + + return DisjunctionInfoBuilder(/*score=*/favoredChoices.empty() ? 0 : 1, + favoredChoices) + .build(); +} + +} // 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) { + DisjunctionInfoBuilder info(/*score=*/2.0); + for (auto *choice : disjunction->getNestedConstraints()) { + if (choice->isFavored()) + info.addFavoredChoice(choice); + } + recordResult(disjunction, info.build()); + 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, + DisjunctionInfoBuilder(/*score=*/1.0, favoredChoices).build()); + 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, + DisjunctionInfoBuilder(/*score=*/0.01, favoredChoices).build()); + continue; + } + } + + // 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. + if (auto info = preserveFavoringOfUnlabeledUnaryArgument(cs, disjunction, + argumentList)) { + recordResult(disjunction, std::move(info.value())); + 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) {} + }; + + // Determine whether there are any non-speculative choices + // in the given set of candidates. Speculative choices are + // literals or types inferred from initializer calls. + auto anyNonSpeculativeCandidates = + [&](ArrayRef candidates) { + // If there is only one (non-CGFloat) candidate inferred from + // an initializer call we don't consider this a speculation. + // + // CGFloat inference is always speculative because of the + // implicit conversion between Double and CGFloat. + if (llvm::count_if(candidates, [&](const auto &candidate) { + return candidate.fromInitializerCall && + !candidate.type->isCGFloat(); + }) == 1) + return true; + + // If there are no non-literal and non-initializer-inferred types + // in the list, consider this is a speculation. + return llvm::any_of(candidates, [&](const auto &candidate) { + return !candidate.fromLiteral && !candidate.fromInitializerCall; + }); + }; + + auto anyNonSpeculativeResultTypes = [](ArrayRef results) { + return llvm::any_of(results, [](Type resultTy) { + // Double and CGFloat are considered speculative because + // there exists an implicit conversion between them and + // preference is based on score impact in the overall solution. + return !(resultTy->isDouble() || resultTy->isCGFloat()); + }); + }; + + SmallVector, 2> + argumentCandidates; + argumentCandidates.resize(argFuncType->getNumParams()); + + llvm::TinyPtrVector resultTypes; + + bool hasArgumentCandidates = false; + bool isOperator = isOperatorDisjunction(disjunction); + + for (unsigned i = 0, n = argFuncType->getNumParams(); i != n; ++i) { + const auto ¶m = argFuncType->getParams()[i]; + auto argType = cs.simplifyType(param.getPlainType()); + + SmallVector optionals; + // i.e. `??` operator could produce an optional type + // so `test(<> ?? 0) could result in an optional + // argument that wraps a type variable. It should be possible + // to infer bindings from underlying type variable and restore + // optionality. + if (argType->hasTypeVariable()) { + if (auto *typeVar = argType->lookThroughAllOptionalTypes(optionals) + ->getAs()) + argType = typeVar; + } + + SmallVector types; + if (auto *typeVar = argType->getAs()) { + auto bindingSet = cs.getBindingsFor(typeVar); + + // We need to have a notion of "complete" binding set before + // we can allow inference from generic parameters and ternary, + // otherwise we'd make a favoring decision that might not be + // correct i.e. `v ?? (<> ? nil : o)` where `o` is `Int`. + // `getBindingsFor` doesn't currently infer transitive bindings + // which means that for a ternary we'd only have a single + // binding - `Int` which could lead to favoring overload of + // `??` and has non-optional parameter on the right-hand side. + if (typeVar->getImpl().getGenericParameter() || + typeVar->getImpl().isTernary()) + continue; + + auto restoreOptionality = [](Type type, unsigned numOptionals) { + for (unsigned i = 0; i != numOptionals; ++i) + type = type->wrapInOptionalType(); + return type; + }; + + for (const auto &binding : bindingSet.Bindings) { + auto type = restoreOptionality(binding.BindingType, optionals.size()); + types.push_back({type}); + } + + for (const auto &literal : bindingSet.Literals) { + if (literal.second.hasDefaultType()) { + // Add primary default type + auto type = restoreOptionality(literal.second.getDefaultType(), + optionals.size()); + types.push_back({type, + /*fromLiteral=*/true}); + } else if (literal.first == + cs.getASTContext().getProtocol( + KnownProtocolKind::ExpressibleByNilLiteral) && + literal.second.IsDirectRequirement) { + // `==` and `!=` operators have special overloads that accept `nil` + // as `_OptionalNilComparisonType` which is preferred over a + // generic form `(T?, T?)`. + if (isOperatorNamed(disjunction, "==") || + isOperatorNamed(disjunction, "!=")) { + auto nilComparisonTy = + cs.getASTContext().get_OptionalNilComparisonTypeType(); + types.push_back({nilComparisonTy, /*fromLiteral=*/true}); + } + } + } + + // Help situations like `1 + {Double, CGFloat}(...)` by inferring + // a type for the second operand of `+` based on a type being + // constructed. + if (typeVar->getImpl().isFunctionResult()) { + auto *resultLoc = typeVar->getImpl().getLocator(); + + if (auto type = inferTypeOfArithmeticOperatorChain( + cs, resultLoc->getAnchor())) { + types.push_back({type, /*fromLiteral=*/true}); + } + + auto binding = + inferTypeFromInitializerResultType(cs, typeVar, disjunctions); + + if (auto instanceTy = binding.getPointer()) { + types.push_back({instanceTy, + /*fromLiteral=*/false, + /*fromInitializerCall=*/true}); + + if (binding.getInt()) + types.push_back({instanceTy->wrapInOptionalType(), + /*fromLiteral=*/false, + /*fromInitializerCall=*/true}); + } + } + } else { + types.push_back({argType, /*fromLiteral=*/false}); + } + + argumentCandidates[i].append(types); + hasArgumentCandidates |= !types.empty(); + } + + 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); + } + + // Infer bindings for each side of a ternary condition. + bindingSet.forEachAdjacentVariable( + [&cs, &resultTypes](TypeVariableType *adjacentVar) { + auto *adjacentLoc = adjacentVar->getImpl().getLocator(); + // This is one of the sides of a ternary operator. + if (adjacentLoc->directlyAt()) { + auto adjacentBindings = cs.getBindingsFor(adjacentVar); + + for (const auto &binding : adjacentBindings.Bindings) + resultTypes.push_back(binding.BindingType); + } + }); + } else { + resultTypes.push_back(resultType); + } + + // Determine whether all of the argument candidates are speculative (i.e. + // literals). This information is going to be used later on when we need to + // decide how to score a matching choice. + bool onlySpeculativeArgumentCandidates = + hasArgumentCandidates && + llvm::none_of( + indices(argFuncType->getParams()), [&](const unsigned argIdx) { + return anyNonSpeculativeCandidates(argumentCandidates[argIdx]); + }); + + bool canUseContextualResultTypes = + isOperator && !isStandardComparisonOperator(disjunction); + + // 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; + } + + auto result = TypeChecker::containsProtocol(candidateType, P, + /*allowMissing=*/false); + return result.first || result.second; + }); + } + + 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, + StringInterpolation = 0x10, + }; + + 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; + } + } + + if (options.contains(MatchFlag::ExactOnly)) { + // If an exact match is requested favor only `[...]` to `Array<...>` + // since everything else is going to increase to score. + if (options.contains(MatchFlag::Literal)) { + if (isUnboundArrayType(candidateType)) + return paramType->isArray() ? 0.3 : 0; + + if (isUnboundDictionaryType(candidateType)) + return cs.isDictionaryType(paramType) ? 0.3 : 0; + } + + if (!areEqual(candidateType, paramType)) + return 0; + return options.contains(MatchFlag::Literal) ? 0.3 : 1; + } + + // Exact match between candidate and parameter types. + if (areEqual(candidateType, paramType)) { + return options.contains(MatchFlag::Literal) ? 0.3 : 1; + } + + if (options.contains(MatchFlag::Literal)) { + if (paramType->hasTypeParameter() || + paramType->isAnyExistentialType()) { + // Attempt to match literal default to generic parameter. + // This helps to determine whether there are any generic + // overloads that are a possible match. + auto score = + scoreCandidateMatch(genericSig, choice, candidateType, + paramType, options - MatchFlag::Literal); + if (score == 0) + return 0; + + // Optional injection lowers the score for operators to match + // pre-optimizer behavior. + return choice->isOperator() && paramType->getOptionalObjectType() + ? 0.2 + : 0.3; + } else { + // Integer and floating-point literals can match any parameter + // type that conforms to `ExpressibleBy{Integer, Float}Literal` + // protocol. Since this assessment is done in isolation we don't + // lower the score even though this would be a non-default binding + // for a literal. + if (candidateType->isInt() && + TypeChecker::conformsToKnownProtocol( + paramType, KnownProtocolKind::ExpressibleByIntegerLiteral)) + return 0.3; + + if (candidateType->isDouble() && + TypeChecker::conformsToKnownProtocol( + paramType, KnownProtocolKind::ExpressibleByFloatLiteral)) + return 0.3; + + if (candidateType->isBool() && + TypeChecker::conformsToKnownProtocol( + paramType, KnownProtocolKind::ExpressibleByBooleanLiteral)) + return 0.3; + + if (candidateType->isString()) { + auto literalProtocol = + options.contains(MatchFlag::StringInterpolation) + ? KnownProtocolKind::ExpressibleByStringInterpolation + : KnownProtocolKind::ExpressibleByStringLiteral; + + if (TypeChecker::conformsToKnownProtocol(paramType, + literalProtocol)) + return 0.3; + } + + auto &ctx = cs.getASTContext(); + + // Check if the other side conforms to `ExpressibleByArrayLiteral` + // protocol (in some way). We want an overly optimistic result + // here to avoid under-favoring. + if (candidateType->isArray() && + checkConformanceWithoutContext( + paramType, + ctx.getProtocol(KnownProtocolKind::ExpressibleByArrayLiteral), + /*allowMissing=*/true)) + return 0.3; + + // Check if the other side conforms to + // `ExpressibleByDictionaryLiteral` protocol (in some way). + // We want an overly optimistic result here to avoid under-favoring. + if (candidateType->isDictionary() && + checkConformanceWithoutContext( + paramType, + ctx.getProtocol( + KnownProtocolKind::ExpressibleByDictionaryLiteral), + /*allowMissing=*/true)) + 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()) { + // Can match i.e. Int? to Int or T to Int? + if ((paramOptionals.empty() && + paramType->is()) || + 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 > 0 && choice->isOperator() ? score.value() - 0.1 + : 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->isArray() && + 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() || + candidateType->hasUnboundGenericType() || + 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 there are no requirements associated with the generic + // parameter or dependent member type it could match any type. + if (requirements.empty()) + return 0.7; + + // 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 system. + if (paramType->hasTypeParameter()) { + auto *candidateDecl = candidateType->getAnyNominal(); + auto *paramDecl = paramType->getAnyNominal(); + + // Conservatively we need to make sure that this is not worse + // than matching against a generic parameter with or without + // requirements. + if (candidateDecl && paramDecl && candidateDecl == paramDecl) { + // If the candidate it not yet fully resolved, let's lower the + // score slightly to avoid over-favoring generic overload choices. + if (candidateType->hasTypeVariable()) + return 0.8; + + // If the candidate is fully resolved we need to treat this + // as we would generic parameter otherwise there is a risk + // of skipping some of the valid choices. + return choice->isOperator() ? 0.9 : 1.0; + } + } + + return 0; + }; + + // The choice with the best score. + double bestScore = 0.0; + SmallVector, 2> favoredChoices; + + 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; + + // Require exact matches only if all of the arguments + // are literals and there are no usable contextual result + // types that could help narrow favored choices. + bool favorExactMatchesOnly = + onlySpeculativeArgumentCandidates && + (!canUseContextualResultTypes || resultTypes.empty()); + + // 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(); + + if (paramFlags.isAutoClosure()) + paramType = paramType->castTo()->getResult(); + + // 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; + + if (isExpr( + argumentList->getExpr(argIdx) + ->getSemanticsProvidingExpr())) + options |= MatchFlag::StringInterpolation; + + // 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; + } + + if (!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); + + // If one of the result types matches exactly, that's a good + // indication that overload choice should be favored. + // + // It's only safe to match result types of operators + // because regular functions/methods/subscripts and + // especially initializers could end up with a lot of + // favored overloads because on the result type alone. + if (canUseContextualResultTypes && + (score > 0 || !hasArgumentCandidates)) { + if (llvm::any_of(resultTypes, [&](const Type candidateResultTy) { + 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.01; + } + + 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); + + // Determine if the score and favoring decisions here are + // based only on "speculative" sources i.e. inference from + // literals. + // + // This information is going to be used by the disjunction + // selection algorithm to prevent over-eager selection of + // the operators over unsupported non-operator declarations. + bool isSpeculative = onlySpeculativeArgumentCandidates && + (!canUseContextualResultTypes || + !anyNonSpeculativeResultTypes(resultTypes)); + + DisjunctionInfoBuilder info(/*score=*/bestScore); + + info.setSpeculative(isSpeculative); + + for (const auto &choice : favoredChoices) { + if (choice.second == bestScore) + info.addFavoredChoice(choice.first); + } + + recordResult(disjunction, info.build()); + } + + 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"; + } +} + +static std::optional isPreferable(ConstraintSystem &cs, + Constraint *disjunctionA, + Constraint *disjunctionB) { + // Consider only operator vs. non-operator situations. + if (isOperatorDisjunction(disjunctionA) == + isOperatorDisjunction(disjunctionB)) + return std::nullopt; + + // Prevent operator selection if its passed as an argument + // to not-yet resolved call. This helps to make sure that + // in result builder context chained members and other + // non-operator disjunctions are always selected first, + // because they provide the context and help to prune the system. + if (isInResultBuilderContext(cs, disjunctionA)) { + if (isOperatorDisjunction(disjunctionA)) { + if (isOperatorPassedToUnresolvedCall(cs, disjunctionA)) + return false; + } else { + if (isOperatorPassedToUnresolvedCall(cs, disjunctionB)) + return true; + } + } + + return std::nullopt; +} + +std::optional>> +ConstraintSystem::selectDisjunction() { + SmallVector disjunctions; + + collectDisjunctions(disjunctions); + if (disjunctions.empty()) + return std::nullopt; + + llvm::DenseMap favorings; + determineBestChoicesInContext(*this, disjunctions, favorings); + + // 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(); + + if (firstActive == 1 || secondActive == 1) + return secondActive != 1; + + if (auto preference = isPreferable(*this, first, second)) + return preference.value(); + + auto &[firstScore, firstFavoredChoices, isFirstSpeculative] = + favorings[first]; + auto &[secondScore, secondFavoredChoices, isSecondSpeculative] = + favorings[second]; + + bool isFirstOperator = isOperatorDisjunction(first); + bool isSecondOperator = isOperatorDisjunction(second); + + // Not all of the non-operator disjunctions are supported by the + // ranking algorithm, so to prevent eager selection of operators + // when nothing concrete is known about them, let's reset the score + // and compare purely based on number of choices. + if (isFirstOperator != isSecondOperator) { + if (isFirstOperator && isFirstSpeculative) + firstScore = 0; + + if (isSecondOperator && isSecondSpeculative) + secondScore = 0; + } + + // 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; + + // If the scores are the same and both disjunctions are operators + // they could be ranked purely based on whether the candidates + // were speculative or not. The one with more context always wins. + // + // Consider the following situation: + // + // func test(_: Int) { ... } + // func test(_: String) { ... } + // + // test("a" + "b" + "c") + // + // In this case we should always prefer ... + "c" over "a" + "b" + // because it would fail and prune the other overload if parameter + // type (aka contextual type) is `Int`. + if (isFirstOperator && isSecondOperator && + isFirstSpeculative != isSecondSpeculative) + return isSecondSpeculative; + } + + // Use favored choices only if disjunction score is higher + // than zero. This means that we can maintain favoring + // choices without impacting selection decisions. + unsigned numFirstFavored = + firstScore.value_or(0) ? firstFavoredChoices.size() : 0; + unsigned numSecondFavored = + secondScore.value_or(0) ? secondFavoredChoices.size() : 0; + + 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 fa8b19072360d..59bc41315eb72 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -10191,7 +10191,6 @@ 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()) { @@ -10199,17 +10198,6 @@ 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); - } - } - } } } @@ -10318,30 +10306,6 @@ 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'. @@ -15170,9 +15134,19 @@ ConstraintSystem::simplifyRestrictedConstraintImpl( restriction == ConversionRestrictionKind::CGFloatToDouble ? 2 : 10; if (restriction == ConversionRestrictionKind::DoubleToCGFloat) { - if (auto *anchor = locator.trySimplifyToExpr()) { - if (auto depth = getExprDepth(anchor)) - impact = (*depth + 1) * impact; + 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; } } else if (locator.directlyAt() || locator.endsWith()) { diff --git a/lib/Sema/CSSolver.cpp b/lib/Sema/CSSolver.cpp index f5689a4f1f1ec..41476c3f11849 100644 --- a/lib/Sema/CSSolver.cpp +++ b/lib/Sema/CSSolver.cpp @@ -796,562 +796,6 @@ 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; - if (!CT->hasUnboundGenericType()) { - 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; - - // Avoid handling InlineArray, building a tuple would be wrong, and - // we want to eliminate shrink. - if (boundGeneric->getDecl() == ctx.getInlineArrayDecl()) - return Type(); - - 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); - } -} - bool constraints::debugConstraintSolverForTarget( ASTContext &C, SyntacticElementTarget target) { if (C.TypeCheckerOpts.DebugConstraintSolver) @@ -1718,8 +1162,6 @@ bool ConstraintSystem::solveForCodeCompletion( // Set up the expression type checker timer. startExpressionTimer(expr); - - shrink(expr); } if (isDebugMode()) { @@ -1852,63 +1294,6 @@ 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().gatherNearbyConstraints( - typeVar, - [](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( TypeVariableType *typeVar, OptionalWrappingDirection optionalDirection, @@ -2013,6 +1398,27 @@ 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) @@ -2289,6 +1695,8 @@ void DisjunctionChoiceProducer::partitionDisjunction( // end of the partitioning. SmallVector favored; SmallVector everythingElse; + // Disfavored choices are part of `everythingElse` but introduced at the end. + SmallVector disfavored; SmallVector simdOperators; SmallVector disabled; SmallVector unavailable; @@ -2321,6 +1729,11 @@ void DisjunctionChoiceProducer::partitionDisjunction( everythingElse.push_back(index); return true; } + + if (decl->getAttrs().hasAttribute()) { + disfavored.push_back(index); + return true; + } } return false; @@ -2364,6 +1777,9 @@ void DisjunctionChoiceProducer::partitionDisjunction( return true; }); + // Introduce disfavored choices at the end. + everythingElse.append(disfavored); + // Local function to create the next partition based on the options // passed in. PartitionAppendCallback appendPartition = @@ -2383,61 +1799,6 @@ 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 261d2c2e183e6..5e455e8b2441c 100644 --- a/lib/Sema/CSStep.cpp +++ b/lib/Sema/CSStep.cpp @@ -272,7 +272,7 @@ StepResult ComponentStep::take(bool prevFailed) { } }); - auto *disjunction = CS.selectDisjunction(); + auto disjunction = CS.selectDisjunction(); auto *conjunction = CS.selectConjunction(); if (CS.isDebugMode()) { @@ -315,7 +315,8 @@ 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)) + if (disjunction && + !bestBindings->favoredOverDisjunction(disjunction->first)) return StepKind::Disjunction; if (conjunction && !bestBindings->favoredOverConjunction(conjunction)) @@ -338,9 +339,9 @@ StepResult ComponentStep::take(bool prevFailed) { return suspend( std::make_unique(*bestBindings, Solutions)); case StepKind::Disjunction: { - CS.retireConstraint(disjunction); + CS.retireConstraint(disjunction->first); return suspend( - std::make_unique(CS, disjunction, Solutions)); + std::make_unique(CS, *disjunction, Solutions)); } case StepKind::Conjunction: { CS.retireConstraint(conjunction); @@ -634,9 +635,30 @@ bool DisjunctionStep::shouldSkip(const DisjunctionChoice &choice) const { if (choice.isDisabled()) return skip("disabled"); - // Skip unavailable overloads (unless in diagnostic mode). - if (choice.isUnavailable() && !CS.shouldAttemptFixes()) - return skip("unavailable"); + if (!CS.shouldAttemptFixes()) { + // Skip unavailable overloads. + if (choice.isUnavailable()) + return skip("unavailable"); + + // Since the disfavored overloads are always located at the end of + // the partition they could be skipped if there was at least one + // valid solution for this partition already, because the solution + // they produce would always be worse. + if (choice.isDisfavored() && LastSolvedChoice) { + bool canSkipDisfavored = true; + auto &lastScore = LastSolvedChoice->second; + for (unsigned i = 0, n = unsigned(SK_DisfavoredOverload) + 1; i != n; + ++i) { + if (lastScore.Data[i] > 0) { + canSkipDisfavored = false; + break; + } + } + + if (canSkipDisfavored) + return skip("disfavored"); + } + } if (ctx.TypeCheckerOpts.DisableConstraintSolverPerformanceHacks) return false; diff --git a/lib/Sema/CSStep.h b/lib/Sema/CSStep.h index c6f324d619768..048dbbe39615e 100644 --- a/lib/Sema/CSStep.h +++ b/lib/Sema/CSStep.h @@ -607,26 +607,28 @@ 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}, solutions), Disjunction(disjunction) { + : BindingStep(cs, {cs, disjunction, favoredChoices}, 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; @@ -679,49 +681,6 @@ 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()) { - if (constraint->isDisabled()) - continue; - - 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 850f3fc378558..b6f0262d35513 100644 --- a/lib/Sema/Constraint.cpp +++ b/lib/Sema/Constraint.cpp @@ -746,17 +746,6 @@ 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 8b6ae459a75d4..6886726f200a9 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -204,6 +204,18 @@ 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(); +} + +bool TypeVariableType::Implementation::isTernary() const { + return locator && locator->directlyAt(); +} + void *operator new(size_t bytes, ConstraintSystem& cs, size_t alignment) { return cs.getAllocator().Allocate(bytes, alignment); @@ -461,10 +473,6 @@ 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/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index 90445182edff1..6119c3502303b 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -1672,7 +1672,7 @@ class ApplyClassifier { /// Check to see if the given function application throws, is async, or /// involves unsafe behavior. Classification classifyApply( - Expr *call, + ApplyExpr *call, const AbstractFunction &fnRef, Expr *calleeFn, const AnyFunctionType *fnType, @@ -1820,7 +1820,7 @@ class ApplyClassifier { // to fix their code. if (kind == EffectKind::Async && fnRef.getKind() == AbstractFunction::Function) { - if (auto *ctor = dyn_cast(calleeFn)) { + if (auto *ctor = dyn_cast(call->getFn())) { if (ctor->getFn()->isImplicit() && args->isUnlabeledUnary()) result.setDowngradeToWarning(true); } diff --git a/lib/Sema/TypeOfReference.cpp b/lib/Sema/TypeOfReference.cpp index e8911387832ab..313da3e045a03 100644 --- a/lib/Sema/TypeOfReference.cpp +++ b/lib/Sema/TypeOfReference.cpp @@ -1900,11 +1900,16 @@ Type ConstraintSystem::getEffectiveOverloadType(ConstraintLocator *locator, type, var, useDC, GetClosureType{*this}, ClosureIsolatedByPreconcurrency{*this}); } else if (isa(decl) || isa(decl)) { - if (decl->isInstanceMember() && - (!overload.getBaseType() || - (!overload.getBaseType()->getAnyNominal() && - !overload.getBaseType()->is()))) - return Type(); + if (decl->isInstanceMember()) { + auto baseTy = overload.getBaseType(); + if (!baseTy) + return Type(); + + baseTy = baseTy->getRValueType(); + if (!baseTy->getAnyNominal() && !baseTy->is() && + !baseTy->is()) + return Type(); + } // Cope with 'Self' returns. if (!decl->getDeclContext()->getSelfProtocolDecl()) { diff --git a/test/Constraints/async.swift b/test/Constraints/async.swift index 52f7a44e46d55..9985f169c0a22 100644 --- a/test/Constraints/async.swift +++ b/test/Constraints/async.swift @@ -212,10 +212,9 @@ 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. - // FIXME: This should produce a warning once type-checker performance hacks are removed. - _ = Test(v) // Temporary okay + _ = 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'}} _ = 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 {{call is 'async'}} + Test(v).test(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note 2 {{call is 'async'}} } diff --git a/test/Constraints/bidirectional_conversions.swift b/test/Constraints/bidirectional_conversions.swift index de5077c507a32..43620893f9693 100644 --- a/test/Constraints/bidirectional_conversions.swift +++ b/test/Constraints/bidirectional_conversions.swift @@ -6,7 +6,7 @@ import CoreGraphics ///////////// -struct G { // expected-note {{arguments to generic parameter 'T' ('CGFloat?' and 'CGFloat') are expected to be equal}} +struct G { var t: T } @@ -32,8 +32,7 @@ func foo4(x: (() -> ())?, y: @escaping @convention(block) () -> ()) -> G<() -> ( func foo5(x: CGFloat?, y: Double) -> G { let g = G(t: x ?? y) - // FIXME - return g // expected-error {{cannot convert return expression of type 'G' to return type 'G'}} + return g } func foo6(x: Double?, y: CGFloat) -> G { diff --git a/test/Constraints/casts_swift6.swift b/test/Constraints/casts_swift6.swift index 5161a493e9dbd..17cbd89100741 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' ('[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]')}} + _ = (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]')}} _ = (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 32f3bf3c8bb35..bad4415cf63f1 100644 --- a/test/Constraints/common_type.swift +++ b/test/Constraints/common_type.swift @@ -1,6 +1,8 @@ // 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 014b112f8de10..1915540fdd571 100644 --- a/test/Constraints/implicit_double_cgfloat_conversion.swift +++ b/test/Constraints/implicit_double_cgfloat_conversion.swift @@ -337,19 +337,6 @@ func test_implicit_conversion_clash_with_partial_application_check() { } } -// rdar://99352676 -// CHECK-LABEL: sil hidden [ossa] @$s34implicit_double_cgfloat_conversion20test_init_validationyyF : $@convention(thin) () -> () { -func test_init_validation() { - class Foo { - static let bar = 100.0 - - func getBar() -> CGFloat? { - return Self.bar - // CHECK: function_ref @$s12CoreGraphics7CGFloatVyACSdcfC : $@convention(method) (Double, @thin CGFloat.Type) -> CGFloat - } - } -} - func test_ternary_and_nil_coalescing() { func test(_: Double?) {} @@ -361,3 +348,59 @@ func test_ternary_and_nil_coalescing() { test(v ?? 0.0) // Ok } } + +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?) { + // Make sure that @autoclosure thunk calls CGFloat./ and not Double./ + // CHECK-LABEL: sil private [transparent] [ossa] @$s34implicit_double_cgfloat_conversion05test_C45_operator_is_attempted_with_literal_arguments1vy12CoreGraphics7CGFloatVSg_tFAFyKXEfu_ + // CHECK: [[CGFLOAT_DIV_OP:%.*]] = function_ref @$s12CoreGraphics7CGFloatV34implicit_double_cgfloat_conversionE1doiyA2C_ACtFZ : $@convention(method) (CGFloat, CGFloat, @thin CGFloat.Type) -> CGFloat + // CHECK-NEXT: {{.*}} = apply [[CGFLOAT_DIV_OP]]({{.*}}, %2) : $@convention(method) (CGFloat, CGFloat, @thin CGFloat.Type) -> 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 +} + +// rdar://99352676 +// CHECK-LABEL: sil private [ossa] @$s34implicit_double_cgfloat_conversion20test_init_validationyyF3FooL_C6getBar12CoreGraphics7CGFloatVSgyF : $@convention(method) (@guaranteed Foo) -> Optional +func test_init_validation() { + class Foo { + static let bar = 100.0 + + func getBar() -> CGFloat? { + return Self.bar + // CHECK: function_ref @$s12CoreGraphics7CGFloatVyACSdcfC : $@convention(method) (Double, @thin CGFloat.Type) -> CGFloat + } + } +} diff --git a/test/Constraints/member_import_visibility.swift b/test/Constraints/member_import_visibility.swift new file mode 100644 index 0000000000000..cda1c01513a7a --- /dev/null +++ b/test/Constraints/member_import_visibility.swift @@ -0,0 +1,39 @@ +// 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/nil-coalescing-favoring.swift b/test/Constraints/nil-coalescing-favoring.swift index 1723900d5a58d..5caa9ca548156 100644 --- a/test/Constraints/nil-coalescing-favoring.swift +++ b/test/Constraints/nil-coalescing-favoring.swift @@ -12,3 +12,53 @@ struct A { self.init(other ?? ._none) } } + +do { + class Super {} + class Sub: Super {} + + func flatMap(_: (Int) -> R?) -> R? {} + + func test() { + let dict: Dictionary + let sup: Super + + // CHECK: declref_expr type="(consuming Super?, @autoclosure () throws -> Super) throws -> Super" {{.*}} decl="Swift.(file).?? + let x = flatMap { dict[$0] } ?? sup // Ok + let _: Super = x + } +} + +// Reduced from vapor project. Favoring _only_ an overload of `??` and takes `T?` as a second parameter would result in an invalid solution. +extension Array where Element == UInt8 { + init?(decodingBase32 str: String) { + guard let decoded = str.utf8.withContiguousStorageIfAvailable({ Array(decodingBase32: $0) }) ?? Array(decodingBase32: Array(str.utf8)) else { // Ok + return nil + } + self = decoded + } + + init?(decodingBase32 bytes: C) where C: RandomAccessCollection, C.Element == UInt8, C.Index == Int { + fatalError() + } +} + +func test_no_incorrect_favoring(v: Int?, o: Int) { + func ternary(_: T, _: T) -> T { fatalError() } + + func nilCoelesing(_: T?, _: T) -> T { fatalError() } + func nilCoelesing(_: T?, _: T?) -> T? { fatalError() } + + let t1 = v ?? (true ? nil : v) + let t2 = v ?? ternary(nil, o) + + let s1 = nilCoelesing(v, (true ? nil : v)) + let s2 = nilCoelesing(v, ternary(nil, o)) + + func sameType(_: T, as: T.Type) {} + + sameType(t1, as: Int?.self) + sameType(t2, as: Int?.self) + sameType(s1, as: Int?.self) + sameType(s2, as: Int?.self) +} diff --git a/test/Constraints/old_hack_related_ambiguities.swift b/test/Constraints/old_hack_related_ambiguities.swift new file mode 100644 index 0000000000000..d94dc86cc3ae3 --- /dev/null +++ b/test/Constraints/old_hack_related_ambiguities.swift @@ -0,0 +1,385 @@ +// 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? { + // Because calls had favored types set if they were resolved during constraint generation. + 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) { + // The old hack (which is now removed) couldn't handle member references, only direct declaration references. + guard let x = UnsafeMutablePointer(OpaquePointer(self.p)) else { + return + } + _ = x + + guard let x = UnsafeMutablePointer(OpaquePointer(p)) else { + // expected-error@-1 {{initializer for conditional binding must have Optional type, not 'UnsafeMutablePointer'}} + 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 +} + +// This should be ambiguous without contextual type but was accepted before during to +// unlabeled unary argument favoring. +func test_variadic_static_member_is_preferred_over_partially_applied_instance_overload() { + struct Test { + func fn() {} + static func fn(_: Test...) {} + } + + let t: Test + Test.fn(t) // Ok +} + +// Unary unlabeled argument favoring hacks never applied to subscripts + +protocol Subscriptable { +} + +extension Subscriptable { + subscript(key: String) -> Any? { nil } +} + +struct MyValue {} + +extension Dictionary : Subscriptable {} + +func test_that_unary_argument_hacks_do_not_apply_to_subscripts(dict: [String: MyValue]) { + let value = dict["hello"] + let _: MyValue? = value // Ok +} + +// Unlabeled unary argument hack was disabled if there were any protocol requirements +// or variadic generic overloads present in the result set (regadless of their viability). +// +// Remove the requirement and variadic overloads and this code would start failing even +// though it shouldn't! + +struct Future { +} + +protocol DB { + func get(_: Int, _: Int) -> Future +} + +extension DB { + func get(_: Int, _: Int = 42) async throws -> Int? { nil } + func get(_: Int) -> Future { .init() } + + func fetch(_: Int, _: Int = 42) async throws -> Int? { nil } + func fetch(_: Int) -> Future { .init() } + func fetch(values: repeat each T) -> Int { 42 } +} + +struct TestUnary { + var db: any DB + + func get(v: Int) async throws { + guard let _ = try await self.db.get(v) else { // Ok + return + } + + guard let _ = try await self.db.fetch(v) else { // Ok + return + } + } +} + +// Prevent non-optional overload of `??` to be favored when all initializers are failable. + +class A {} +class B {} + +protocol P { + init() +} + +extension P { + init?(v: A) { self.init() } +} + +struct V : P { + init() {} + + @_disfavoredOverload + init?(v: B?) {} + + // Important to keep this to make sure that disabled constraints + // are handled properly. + init(other: T) where T.Element == Character {} +} + +class TestFailableOnly { + var v: V? + + func test(defaultB: B) { + guard let _ = self.v ?? V(v: defaultB) else { // OK (no warnings) + return + } + } +} + +do { + @_disfavoredOverload + func test(over: Int, that: String = "", block: @escaping (Int) throws -> Void) async throws {} + func test(over: Int, that: String = "", block: @escaping (Int) throws -> Void) throws {} // expected-note {{found this candidate}} + func test(over: Int, other: String = "", block: @escaping (Int) throws -> Void) throws {} // expected-note {{found this candidate}} + + func performLocal(v: Int, block: @escaping (Int) throws -> Void) async throws { + try await test(over: v, block: block) // expected-error {{ambiguous use of 'test'}} + } + + // The hack applied only to `OverloadedDeclRefExpr`s. + struct MemberTest { + @_disfavoredOverload + func test(over: Int, that: String = "", block: @escaping (Int) throws -> Void) async throws {} + func test(over: Int, that: String = "", block: @escaping (Int) throws -> Void) throws {} + func test(over: Int, other: String = "", block: @escaping (Int) throws -> Void) throws {} + + func performMember(v: Int, block: @escaping (Int) throws -> Void) async throws { + try await test(over: v, block: block) // Ok + } + } +} diff --git a/test/Constraints/operator.swift b/test/Constraints/operator.swift index a54ff50a4770d..d199f88c65357 100644 --- a/test/Constraints/operator.swift +++ b/test/Constraints/operator.swift @@ -330,3 +330,66 @@ 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/Constraints/overload.swift b/test/Constraints/overload.swift index 87c132e21c5ac..699c1c06d97cc 100644 --- a/test/Constraints/overload.swift +++ b/test/Constraints/overload.swift @@ -350,6 +350,28 @@ do { } } +// Make sure that the solver properly handles mix of non-default integer and floating-point literals +do { + func test( + withInitialValue initialValue: Float, + increment: Float, + count: Int) -> [Float] {} + + func test( + withInitialValue initialValue: Double, + increment: Double, + count: Int) -> [Double] {} + + + func testDoubleVsFloat(count: Int) { + let returnedResult = test(withInitialValue: 0, + increment: 0.1, + count: count) + + let _: [Double] = returnedResult // Ok + } +} + do { struct S { let x: Int diff --git a/test/IDE/complete_operators.swift b/test/IDE/complete_operators.swift index 2772678e1dbc2..f7e0392a2b5a6 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: ***[#Double#] +// POSTFIX_7: Decl[PostfixOperatorFunction]/CurrModule/TypeRelation[Convertible]: ***[#Double#] func testPostfix8(x: S) { x#^POSTFIX_8^# diff --git a/test/SILOptimizer/infinite_recursion.swift b/test/SILOptimizer/infinite_recursion.swift index 42e64ce15ff2f..c794ba76d24e6 100644 --- a/test/SILOptimizer/infinite_recursion.swift +++ b/test/SILOptimizer/infinite_recursion.swift @@ -282,7 +282,7 @@ public class U { } func == (l: S?, r: S?) -> Bool { - if l == nil && r == nil { return true } // expected-warning {{function call causes an infinite recursion}} + if l == nil && r == nil { return true } guard let l = l, let r = r else { return false } return l === r } diff --git a/test/expr/expressions.swift b/test/expr/expressions.swift index b30d808a56264..2cb5a80245809 100644 --- a/test/expr/expressions.swift +++ b/test/expr/expressions.swift @@ -758,10 +758,11 @@ func invalidDictionaryLiteral() { //===----------------------------------------------------------------------===// // nil/metatype comparisons //===----------------------------------------------------------------------===// -_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'nil' always returns false}} -_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'nil' always returns false}} -_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'nil' always returns true}} -_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'nil' always returns true}} +_ = 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 == .none // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'Optional.none' always returns false}} _ = .none == Int.self // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'Optional.none' always returns false}} diff --git a/validation-test/Sema/SwiftUI/swiftui_multiple_chained_members_in_inner_closure.swift b/validation-test/Sema/SwiftUI/swiftui_multiple_chained_members_in_inner_closure.swift new file mode 100644 index 0000000000000..dbfc309569c85 --- /dev/null +++ b/validation-test/Sema/SwiftUI/swiftui_multiple_chained_members_in_inner_closure.swift @@ -0,0 +1,34 @@ +// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx12 -solver-scope-threshold=10000 + +// REQUIRES: OS=macosx +// REQUIRES: objc_interop + +import Foundation +import SwiftUI + +struct MyView: View { + public enum Style { + case focusRing(platterSize: CGSize, stroke: CGFloat, offset: CGFloat) + } + + var style: Style + var isFocused: Bool + var focusColor: Color + + var body: some View { + Group { + switch style { + case let .focusRing(platterSize: platterSize, stroke: focusRingStroke, offset: focusRingOffset): + Circle() + .overlay { + Circle() + .stroke(isFocused ? focusColor : Color.clear, lineWidth: focusRingStroke) + .frame( + width: platterSize.width + (2 * focusRingOffset) + focusRingStroke, + height: platterSize.height + (2 * focusRingOffset) + focusRingStroke + ) + } + } + } + } +} diff --git a/validation-test/Sema/custom_array_literal_with_operators.swift b/validation-test/Sema/custom_array_literal_with_operators.swift new file mode 100644 index 0000000000000..dd064743356bc --- /dev/null +++ b/validation-test/Sema/custom_array_literal_with_operators.swift @@ -0,0 +1,39 @@ +// RUN: %target-typecheck-verify-swift + +protocol ScalarOrArray { +} + +protocol SpecialValue: ScalarOrArray { +} + +extension Array: ScalarOrArray where Element: SpecialValue { +} + +struct CustomArray : ScalarOrArray { +} + +extension CustomArray : ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Int32...) { + self.init() + } +} + +extension Int32: SpecialValue { +} + +func +(_: T, _: CustomArray) -> CustomArray {} +func +(_: CustomArray, _: T) -> CustomArray {} +func +(_: CustomArray, _: CustomArray) -> CustomArray {} + +extension Sequence where Element == Int { + var asInt32: [Int32] { [] } +} + +extension Array where Element == Int { + var asInt32: [Int32] { [] } +} + +func test(v: Int32, b: [Int]) -> [Int32] { + let result = [1, v - v] + b.asInt32 + return result // Ok +} diff --git a/validation-test/Sema/implicit_cgfloat_double_conversion_correctness.swift b/validation-test/Sema/implicit_cgfloat_double_conversion_correctness.swift index 121812a279c67..4aeb205e7f129 100644 --- a/validation-test/Sema/implicit_cgfloat_double_conversion_correctness.swift +++ b/validation-test/Sema/implicit_cgfloat_double_conversion_correctness.swift @@ -47,3 +47,8 @@ func test_atan_ambiguity(points: (CGPoint, CGPoint)) { test = atan((points.1.y - points.0.y) / (points.1.x - points.0.x)) // Ok _ = test } + +func test_ambigity_with_generic_funcs(a: CGFloat, b: CGFloat) -> [CGFloat] { + let result = [round(abs(a - b) * 100) / 100.0] + return result +} diff --git a/validation-test/Sema/issue78371.swift b/validation-test/Sema/issue78371.swift new file mode 100644 index 0000000000000..e986ad173e291 --- /dev/null +++ b/validation-test/Sema/issue78371.swift @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 0000000000000..7ed8a24e02026 --- /dev/null +++ b/validation-test/Sema/rdar143799118.swift @@ -0,0 +1,23 @@ +// 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 1ce9d4293dc49..873889b4a20bd 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 -solver-disable-shrink +// RUN: %target-typecheck-verify-swift // Self-contained test case protocol P1 {}; func f(_: T, _: T) -> T { fatalError() } 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 new file mode 100644 index 0000000000000..49ce8b4d6abac --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/array_literal_with_operators_and_double_literals.swift @@ -0,0 +1,31 @@ +// 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 new file mode 100644 index 0000000000000..2cd48795d50c5 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/array_of_literals_and_operators_cast_to_float.swift.gyb @@ -0,0 +1,10 @@ +// 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/fast/builder_with_nested_closures.swift b/validation-test/Sema/type_checker_perf/fast/builder_with_nested_closures.swift new file mode 100644 index 0000000000000..e4c1ebb55efd4 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/builder_with_nested_closures.swift @@ -0,0 +1,71 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=200 +// REQUIRES: tools-release,no_asan +// REQUIRES: OS=macosx + +struct Time { + static func +(_: Time, _: Double) -> Time { + Time() + } + + static func now() -> Time { Time() } +} + +struct Queue { + static let current = Queue() + + func after(deadline: Time, execute: () -> Void) {} + func after(deadline: Time, execute: (Int) -> Void) {} +} + +func compute(_: () -> Void) {} + +protocol P { +} + +@resultBuilder +struct Builder { + static func buildExpression(_ v: T) -> T { + v + } + + static func buildBlock(_ v: T) -> T { + v + } + + static func buildBlokc(_ v0: T0, _ v1: T1) -> (T0, T1) { + (v0, v1) + } +} + +struct MyP: P { + func onAction(_: () -> Void) -> some P { self } +} + +class Test { + var value = 0.0 + + @Builder func test() -> some P { + MyP().onAction { + Queue.current.after(deadline: .now() + 0.1) { + compute { + value = 0.3 + Queue.current.after(deadline: .now() + 0.2) { + compute { + value = 1.0 + Queue.current.after(deadline: .now() + 0.2) { + compute { + value = 0.3 + Queue.current.after(deadline: .now() + 0.2) { + compute { + value = 1.0 + } + } + } + } + } + } + } + } + } + } +} 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 new file mode 100644 index 0000000000000..ebf7490e9cfdf --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/cgfloat_with_unary_operators.swift @@ -0,0 +1,19 @@ +// 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/collection_subscript_chains.swift.gyb b/validation-test/Sema/type_checker_perf/fast/collection_subscript_chains.swift.gyb new file mode 100644 index 0000000000000..b6c74599d5707 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/collection_subscript_chains.swift.gyb @@ -0,0 +1,13 @@ +// RUN: %scale-test --begin 1 --end 15 --step 1 --select NumLeafScopes %s --expected-exit-code 0 +// REQUIRES: asserts,no_asan + +func test(carrierDict: [String : Double]) { + var exhaustTemperature: Double + exhaustTemperature = ( + (carrierDict[""] ?? 0.0) + +%for i in range(N): + (carrierDict[""] ?? 0.0) + +%end + (carrierDict[""] ?? 0.0) + ) / 4 +} 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 new file mode 100644 index 0000000000000..c9f3af5130530 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/complex_swiftui_padding_conditions.swift @@ -0,0 +1,17 @@ +// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx10.15 -solver-scope-threshold=5500 +// 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/contextual_cgfloat_type_with_operator_chain_arguments.swift b/validation-test/Sema/type_checker_perf/fast/contextual_cgfloat_type_with_operator_chain_arguments.swift new file mode 100644 index 0000000000000..72fb47673c4ea --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/contextual_cgfloat_type_with_operator_chain_arguments.swift @@ -0,0 +1,17 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=50 + +// REQUIRES: OS=macosx,no_asan +// REQUIRES: objc_interop + +import Foundation + +struct Size { + var width: CGFloat + var height: CGFloat +} + +func frame(width: CGFloat?, height: CGFloat?) {} + +func test(size: Size?) { + frame(width: ((size?.width ?? 0) * 1) + 1.0, height: ((size?.height ?? 0) * 1) + 1.0) +} 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 new file mode 100644 index 0000000000000..a022b5c29a3d3 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/leading_dot_syntax_in_literal_array.swift.gyb @@ -0,0 +1,23 @@ +// 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 new file mode 100644 index 0000000000000..d00b81af40f38 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/member_chains_and_operators_with_iou_base_types.swift @@ -0,0 +1,35 @@ +// 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/slow/nil_coalescing.swift.gyb b/validation-test/Sema/type_checker_perf/fast/nil_coalescing.swift.gyb similarity index 58% rename from validation-test/Sema/type_checker_perf/slow/nil_coalescing.swift.gyb rename to validation-test/Sema/type_checker_perf/fast/nil_coalescing.swift.gyb index 2ef5c8f16a3d7..af8639d9bc159 100644 --- a/validation-test/Sema/type_checker_perf/slow/nil_coalescing.swift.gyb +++ b/validation-test/Sema/type_checker_perf/fast/nil_coalescing.swift.gyb @@ -1,4 +1,4 @@ -// RUN: %scale-test --invert-result --begin 1 --end 5 --step 1 --select NumLeafScopes %s +// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s // REQUIRES: asserts,no_asan func t(_ x: Int?) -> Int { diff --git a/validation-test/Sema/type_checker_perf/fast/nil_coalescing_dictionary_values.swift.gyb b/validation-test/Sema/type_checker_perf/fast/nil_coalescing_dictionary_values.swift.gyb new file mode 100644 index 0000000000000..125e6b4218266 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/nil_coalescing_dictionary_values.swift.gyb @@ -0,0 +1,10 @@ +// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s -Xfrontend=-typecheck +// REQUIRES: asserts, no_asan + +let x: Int? + +let _ = [ +%for i in range(0, N): + "${i}" : x ?? 0, +%end +] diff --git a/validation-test/Sema/type_checker_perf/fast/operator_chain_with_closures.swift.gyb b/validation-test/Sema/type_checker_perf/fast/operator_chain_with_closures.swift.gyb new file mode 100644 index 0000000000000..2703b185b23f1 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/operator_chain_with_closures.swift.gyb @@ -0,0 +1,15 @@ +// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s +// REQUIRES: asserts,no_asan + +struct Test { + var values: [Int] +} + +func test(t: [Test]) { + let _ = 0 + + 1 +%for i in range(1, N): + + 1 +%end + + t.map(\.values.count).reduce(0, +) +} diff --git a/validation-test/Sema/type_checker_perf/slow/operator_chain_with_hetergeneous_arguments.swift b/validation-test/Sema/type_checker_perf/fast/operator_chain_with_hetergeneous_arguments.swift similarity index 90% rename from validation-test/Sema/type_checker_perf/slow/operator_chain_with_hetergeneous_arguments.swift rename to validation-test/Sema/type_checker_perf/fast/operator_chain_with_hetergeneous_arguments.swift index 5d6c8c4440ece..787509fb565b2 100644 --- a/validation-test/Sema/type_checker_perf/slow/operator_chain_with_hetergeneous_arguments.swift +++ b/validation-test/Sema/type_checker_perf/fast/operator_chain_with_hetergeneous_arguments.swift @@ -5,5 +5,4 @@ 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/operator_chains_separated_by_ternary.swift.gyb b/validation-test/Sema/type_checker_perf/fast/operator_chains_separated_by_ternary.swift.gyb new file mode 100644 index 0000000000000..9e3a298c66295 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/operator_chains_separated_by_ternary.swift.gyb @@ -0,0 +1,18 @@ +// RUN: %scale-test --begin 1 --end 12 --step 1 --select NumLeafScopes %s +// REQUIRES: asserts,no_asan + +func compute(_: UInt32) { +} + +func test(cond: Bool) { + compute(cond + ? 1 +%for i in range(1, N): + + 1 +%end + : 1 +%for i in range(1, N): + * 1 +%end + ) +} diff --git a/validation-test/Sema/type_checker_perf/fast/operators_inside_closure.swift b/validation-test/Sema/type_checker_perf/fast/operators_inside_closure.swift new file mode 100644 index 0000000000000..49d44feb86b28 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/operators_inside_closure.swift @@ -0,0 +1,9 @@ +// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=10000 +// REQUIRES: tools-release,no_asan + +// Selecting operators from the closure before arguments to `zip` makes this "too complex" +func compute(_ ids: [UInt64]) { + let _ = zip(ids[ids.indices.dropLast()], ids[ids.indices.dropFirst()]).map { pair in + ((pair.0 % 2 == 0) && (pair.1 % 2 == 1)) ? UInt64(pair.1 - pair.0) : 42 + } +} 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 9cd53902f42ad..0f44ea3c31032 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 -solver-disable-shrink +// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 // 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 new file mode 100644 index 0000000000000..9740b4d15f06c --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/rdar133340307.swift @@ -0,0 +1,134 @@ +// 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/fast/rdar144100160.swift b/validation-test/Sema/type_checker_perf/fast/rdar144100160.swift new file mode 100644 index 0000000000000..7e80fd5c3d6c8 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/rdar144100160.swift @@ -0,0 +1,12 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=100 +// REQUIRES: asserts,no_asan + +typealias TimeInterval = Double + +struct Date { + func addingTimeInterval(_: TimeInterval) -> Date { Date() } +} + +func test(date: Date) { + _ = date.addingTimeInterval(TimeInterval(60 * 60 * 24 * 6 + 12 * 60 + 12 + 1)) +} diff --git a/validation-test/Sema/type_checker_perf/slow/rdar17170728.swift b/validation-test/Sema/type_checker_perf/fast/rdar17170728.swift similarity index 74% rename from validation-test/Sema/type_checker_perf/slow/rdar17170728.swift rename to validation-test/Sema/type_checker_perf/fast/rdar17170728.swift index 62c2bb2ea367b..1e64e4194e4b6 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar17170728.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar17170728.swift @@ -5,7 +5,6 @@ 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/slow/rdar22770433.swift b/validation-test/Sema/type_checker_perf/fast/rdar22770433.swift similarity index 74% rename from validation-test/Sema/type_checker_perf/slow/rdar22770433.swift rename to validation-test/Sema/type_checker_perf/fast/rdar22770433.swift index f063e685689de..0c51c9346d7ac 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar22770433.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar22770433.swift @@ -2,7 +2,6 @@ // 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/rdar31742586.swift b/validation-test/Sema/type_checker_perf/fast/rdar31742586.swift new file mode 100644 index 0000000000000..83a3aa1ffa110 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/rdar31742586.swift @@ -0,0 +1,6 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=1000 +// REQUIRES: tools-release,no_asan + +func rdar31742586() -> Double { + return -(1 + 2) + -(3 + 4) + 5 - (-(1 + 2) + -(3 + 4) + 5) +} diff --git a/validation-test/Sema/type_checker_perf/slow/rdar35213699.swift b/validation-test/Sema/type_checker_perf/fast/rdar35213699.swift similarity index 66% rename from validation-test/Sema/type_checker_perf/slow/rdar35213699.swift rename to validation-test/Sema/type_checker_perf/fast/rdar35213699.swift index 5d89ab1541a89..ea333f993b6a3 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar35213699.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar35213699.swift @@ -3,6 +3,4 @@ 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/slow/rdar46713933_literal_arg.swift b/validation-test/Sema/type_checker_perf/fast/rdar46713933_literal_arg.swift similarity index 85% rename from validation-test/Sema/type_checker_perf/slow/rdar46713933_literal_arg.swift rename to validation-test/Sema/type_checker_perf/fast/rdar46713933_literal_arg.swift index a0628335b9c36..5256a92a787c7 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar46713933_literal_arg.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar46713933_literal_arg.swift @@ -8,5 +8,4 @@ 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/rdar47492691.swift b/validation-test/Sema/type_checker_perf/fast/rdar47492691.swift index 47fcdcb0771af..7a25cca734f79 100644 --- a/validation-test/Sema/type_checker_perf/fast/rdar47492691.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar47492691.swift @@ -7,3 +7,7 @@ import simd func test(foo: CGFloat, bar: CGFloat) { _ = CGRect(x: 0.0 + 1.0, y: 0.0 + foo, width: 3.0 - 1 - 1 - 1.0, height: bar) } + +func test_with_generic_func_and_literals(bounds: CGRect) { + _ = CGRect(x: 0, y: 0, width: 1, height: bounds.height - 2 + bounds.height / 2 + max(bounds.height / 2, bounds.height / 2)) +} diff --git a/validation-test/Sema/type_checker_perf/slow/rdar91310777.swift b/validation-test/Sema/type_checker_perf/fast/rdar91310777.swift similarity index 66% rename from validation-test/Sema/type_checker_perf/slow/rdar91310777.swift rename to validation-test/Sema/type_checker_perf/fast/rdar91310777.swift index eb9dcbee848c8..18450b15fe401 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar91310777.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar91310777.swift @@ -9,7 +9,6 @@ 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/should_skip_bitwise_2.swift b/validation-test/Sema/type_checker_perf/fast/should_skip_bitwise_2.swift index fdcfab315d3ad..d7d37c8b65b93 100644 --- a/validation-test/Sema/type_checker_perf/fast/should_skip_bitwise_2.swift +++ b/validation-test/Sema/type_checker_perf/fast/should_skip_bitwise_2.swift @@ -1,3 +1,3 @@ -// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=2000 +// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=4000 func f896() { let _ = ((0 >> ((0 >> 0) + ((0 / 0) & 0))) >> (0 << ((0 << 0) >> (0 << (0 << 0))))) } diff --git a/validation-test/Sema/type_checker_perf/slow/simd_scalar_multiple.swift.gyb b/validation-test/Sema/type_checker_perf/fast/simd_scalar_multiple.swift.gyb similarity index 70% rename from validation-test/Sema/type_checker_perf/slow/simd_scalar_multiple.swift.gyb rename to validation-test/Sema/type_checker_perf/fast/simd_scalar_multiple.swift.gyb index 497afd10785f5..1d5e4f9e0ea6f 100644 --- a/validation-test/Sema/type_checker_perf/slow/simd_scalar_multiple.swift.gyb +++ b/validation-test/Sema/type_checker_perf/fast/simd_scalar_multiple.swift.gyb @@ -1,4 +1,4 @@ -// RUN: %scale-test --invert-result --begin 1 --end 5 --step 1 --select NumLeafScopes %s +// RUN: %scale-test --begin 1 --end 5 --step 1 --select NumLeafScopes %s // REQUIRES: asserts,no_asan diff --git a/validation-test/Sema/type_checker_perf/fast/string_interpolations.swift.gyb b/validation-test/Sema/type_checker_perf/fast/string_interpolations.swift.gyb new file mode 100644 index 0000000000000..fd54e39b609c7 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/string_interpolations.swift.gyb @@ -0,0 +1,26 @@ +// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s -Xfrontend=-typecheck +// REQUIRES: asserts,no_asan + +struct MyString { +} + +extension MyString: ExpressibleByStringLiteral { + init(stringLiteral value: String) {} +} + +func +(_: MyString, _: MyString) -> MyString {} + +func test(_: @autoclosure () -> String) {} +func test(_: () -> String) {} +func test(_: Character) {} + +func test(names: [String]) { + var resultString = "" + for name in names { + test("\(name)" +%for i in range(0, N): + + "\(name)" +%end + + "\(name)") + } +} diff --git a/validation-test/Sema/type_checker_perf/slow/swift_package_index_1.swift b/validation-test/Sema/type_checker_perf/fast/swift_package_index_1.swift similarity index 77% rename from validation-test/Sema/type_checker_perf/slow/swift_package_index_1.swift rename to validation-test/Sema/type_checker_perf/fast/swift_package_index_1.swift index 934a80472f9c9..2efd34229bc66 100644 --- a/validation-test/Sema/type_checker_perf/slow/swift_package_index_1.swift +++ b/validation-test/Sema/type_checker_perf/fast/swift_package_index_1.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-scope-threshold=1000 +// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=700 // 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 + // expected-error {{the compiler is unable to type-check this expression in reasonable time}} + return fixedByteSize + (port != nil ? 2 : 0) + Int32(comment?.utf8.count ?? 0) + Int32(commentURL?.utf8.count ?? 0) + diff --git a/validation-test/Sema/type_checker_perf/slow/swift_package_index_2.swift.gyb b/validation-test/Sema/type_checker_perf/fast/swift_package_index_2.swift.gyb similarity index 90% rename from validation-test/Sema/type_checker_perf/slow/swift_package_index_2.swift.gyb rename to validation-test/Sema/type_checker_perf/fast/swift_package_index_2.swift.gyb index 08f3063bd26d6..258a967c38021 100644 --- a/validation-test/Sema/type_checker_perf/slow/swift_package_index_2.swift.gyb +++ b/validation-test/Sema/type_checker_perf/fast/swift_package_index_2.swift.gyb @@ -1,8 +1,6 @@ // 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/slow/swift_package_index_4.swift b/validation-test/Sema/type_checker_perf/fast/swift_package_index_4.swift similarity index 94% rename from validation-test/Sema/type_checker_perf/slow/swift_package_index_4.swift rename to validation-test/Sema/type_checker_perf/fast/swift_package_index_4.swift index 8b05bedbea950..eaff8718abba3 100644 --- a/validation-test/Sema/type_checker_perf/slow/swift_package_index_4.swift +++ b/validation-test/Sema/type_checker_perf/fast/swift_package_index_4.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-scope-threshold=60000 +// RUN: %target-swift-frontend -typecheck %s -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 // expected-error {{the compiler is unable to type-check this expression in reasonable time}} + return create <*> 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/slow/swift_package_index_5.swift b/validation-test/Sema/type_checker_perf/fast/swift_package_index_5.swift similarity index 54% rename from validation-test/Sema/type_checker_perf/slow/swift_package_index_5.swift rename to validation-test/Sema/type_checker_perf/fast/swift_package_index_5.swift index e20ea384f9a5f..e05dda77878eb 100644 --- a/validation-test/Sema/type_checker_perf/slow/swift_package_index_5.swift +++ b/validation-test/Sema/type_checker_perf/fast/swift_package_index_5.swift @@ -1,8 +1,8 @@ -// RUN: %target-typecheck-verify-swift -solver-scope-threshold=1000 -swift-version 5 +// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=1000 -swift-version 5 // REQUIRES: tools-release,no_asan func g(_: T) throws { - let _: T? = //expected-error {{the compiler is unable to type-check this expression in reasonable time}} + let _: T? = (try? f() as? T) ?? (try? f() as? T) ?? (try? f() as? T) ?? diff --git a/validation-test/Sema/type_checker_perf/slow/array_count_property_vs_method.swift b/validation-test/Sema/type_checker_perf/slow/array_count_property_vs_method.swift new file mode 100644 index 0000000000000..abcd1cfc545ce --- /dev/null +++ b/validation-test/Sema/type_checker_perf/slow/array_count_property_vs_method.swift @@ -0,0 +1,11 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=5000 +// REQUIRES: tools-release,no_asan +// REQUIRES: OS=macosx + +// FIXME: Array literals are considered "speculative" bindings at the moment but they cannot actually +// assume different types unlike integer and floating-pointer literals. +func f(n: Int, a: [Int]) { + // expected-error@+1 {{the compiler is unable to type-check this expression in reasonable time}} + 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/borderline_flat_map_operator_mix.swift b/validation-test/Sema/type_checker_perf/slow/borderline_flat_map_operator_mix.swift similarity index 50% rename from validation-test/Sema/type_checker_perf/fast/borderline_flat_map_operator_mix.swift rename to validation-test/Sema/type_checker_perf/slow/borderline_flat_map_operator_mix.swift index cca07954da499..b88a73d90121f 100644 --- a/validation-test/Sema/type_checker_perf/fast/borderline_flat_map_operator_mix.swift +++ b/validation-test/Sema/type_checker_perf/slow/borderline_flat_map_operator_mix.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=10 +// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 // REQUIRES: no_asan @@ -10,8 +10,11 @@ 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 + return x.flatMap { z in // expected-error {{the compiler is unable to type-check this expression in reasonable time}} 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/slow/collections_chained_with_plus.swift.gyb b/validation-test/Sema/type_checker_perf/slow/collections_chained_with_plus.swift.gyb new file mode 100644 index 0000000000000..82e558a5a73fb --- /dev/null +++ b/validation-test/Sema/type_checker_perf/slow/collections_chained_with_plus.swift.gyb @@ -0,0 +1,18 @@ +// 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/slow/nil_coalescing_dictionary_values.swift.gyb b/validation-test/Sema/type_checker_perf/slow/nil_coalescing_dictionary_values.swift.gyb deleted file mode 100644 index 878c0a9dc78d1..0000000000000 --- a/validation-test/Sema/type_checker_perf/slow/nil_coalescing_dictionary_values.swift.gyb +++ /dev/null @@ -1,10 +0,0 @@ -// RUN: %scale-test --invert-result --begin 1 --end 8 --step 1 --select NumLeafScopes %s -Xfrontend=-typecheck -// REQUIRES: asserts, no_asan - -let x: Int? - -let _ = [ -%for i in range(0, N): - "k" : x ?? 0, -%end -] diff --git a/validation-test/Sema/type_checker_perf/slow/rdar26564101.swift b/validation-test/Sema/type_checker_perf/slow/rdar26564101.swift index 09ca79329d7a3..e9e1d32c90f05 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 -solver-disable-shrink +// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 // REQUIRES: tools-release,no_asan // UNSUPPORTED: swift_test_mode_optimize_none && OS=linux-gnu diff --git a/validation-test/Sema/type_checker_perf/slow/rdar31742586.swift b/validation-test/Sema/type_checker_perf/slow/rdar31742586.swift deleted file mode 100644 index 0cc33ce8253cf..0000000000000 --- a/validation-test/Sema/type_checker_perf/slow/rdar31742586.swift +++ /dev/null @@ -1,7 +0,0 @@ -// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 -// REQUIRES: tools-release,no_asan - -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/slow/swift_package_index_3.swift b/validation-test/Sema/type_checker_perf/slow/swift_package_index_3.swift index 2327507262418..2ef209606305e 100644 --- a/validation-test/Sema/type_checker_perf/slow/swift_package_index_3.swift +++ b/validation-test/Sema/type_checker_perf/slow/swift_package_index_3.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-scope-threshold=50000 +// RUN: %target-typecheck-verify-swift %s -solver-scope-threshold=50000 // REQUIRES: tools-release,no_asan extension String { diff --git a/validation-test/compiler_crashers_2/35129b57d7eb80f4.swift b/validation-test/compiler_crashers_2_fixed/35129b57d7eb80f4.swift similarity index 70% rename from validation-test/compiler_crashers_2/35129b57d7eb80f4.swift rename to validation-test/compiler_crashers_2_fixed/35129b57d7eb80f4.swift index f1e36b4fb7ab4..043be0648f71a 100644 --- a/validation-test/compiler_crashers_2/35129b57d7eb80f4.swift +++ b/validation-test/compiler_crashers_2_fixed/35129b57d7eb80f4.swift @@ -1,3 +1,3 @@ // {"signature":"(anonymous namespace)::ExprWalker::rewriteTarget(swift::constraints::SyntacticElementTarget)"} -// RUN: not --crash %target-swift-frontend -typecheck %s +// RUN: not %target-swift-frontend -typecheck %s { Sendable(Sendable<