diff --git a/include/swift/Sema/Constraint.h b/include/swift/Sema/Constraint.h index c60ae2a59fb89..89acbceeb255c 100644 --- a/include/swift/Sema/Constraint.h +++ b/include/swift/Sema/Constraint.h @@ -825,11 +825,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 26c681869851a..74ec4e58acac2 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -497,6 +497,14 @@ class TypeVariableType::Implementation { /// literal (represented by `ArrayExpr` and `DictionaryExpr` in AST). bool isCollectionLiteralType() const; + /// Determine whether this type variable represents a literal such + /// as an integer value, a floating-point value with and without a sign. + bool isNumberLiteralType() const; + + /// Determine whether this type variable represents a result type of a + /// function call. + bool isFunctionResult() const; + /// Retrieve the representative of the equivalence class to which this /// type variable belongs. /// @@ -2203,10 +2211,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. /// @@ -2424,75 +2428,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; - } - } - - unsigned threshold = - cs->getASTContext().TypeCheckerOpts.SolverShrinkUnsolvedThreshold; - return unsolvedDisjunctions >= threshold; - } - }; - /// Describes the current solver state. struct SolverState { SolverState(ConstraintSystem &cs, @@ -3016,15 +2951,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. /// @@ -5280,19 +5206,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. /// @@ -5481,11 +5399,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 @@ -6226,7 +6139,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), @@ -6236,6 +6150,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); } @@ -6280,8 +6199,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/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index 9868e51098076..652cd39cd502e 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -13,6 +13,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 0b67bbf022ac3..913ab01db052e 100644 --- a/lib/Sema/CSBindings.cpp +++ b/lib/Sema/CSBindings.cpp @@ -31,6 +31,12 @@ using namespace swift; using namespace constraints; using namespace inference; +/// 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); @@ -1209,7 +1215,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. @@ -2417,17 +2447,35 @@ bool TypeVarBindingProducer::computeNext() { if (binding.Kind == BindingKind::Subtypes || CS.shouldAttemptFixes()) { // If we were unsuccessful solving for T?, try solving for T. if (auto objTy = type->getOptionalObjectType()) { - // If T is a type variable, only attempt this if both the - // type variable we are trying bindings for, and the type - // variable we will attempt to bind, both have the same - // polarity with respect to being able to bind lvalues. - if (auto otherTypeVar = objTy->getAs()) { - if (TypeVar->getImpl().canBindToLValue() == - otherTypeVar->getImpl().canBindToLValue()) { - addNewBinding(binding.withSameSource(objTy, binding.Kind)); + // TODO: This could be generalized in the future to cover all patterns + // that have an intermediate type variable in subtype/conversion chain. + // + // Let's not perform $T? -> $T for closure result types to avoid having + // to re-discover solutions that differ only in location of optional + // injection. + // + // The pattern with such type variables is: + // + // $T_body $T_result $T_contextual_result + // + // When $T_contextual_result is Optional<$U>, the optional injection + // can either happen from $T_body or from $T_result (if `return` + // expression is non-optional), if we allow both the solver would + // find two solutions that differ only in location of optional + // injection. + if (!TypeVar->getImpl().isClosureResultType()) { + // If T is a type variable, only attempt this if both the + // type variable we are trying bindings for, and the type + // variable we will attempt to bind, both have the same + // polarity with respect to being able to bind lvalues. + if (auto otherTypeVar = objTy->getAs()) { + if (TypeVar->getImpl().canBindToLValue() == + otherTypeVar->getImpl().canBindToLValue()) { + addNewBinding(binding.withType(objTy)); + } + } else { + addNewBinding(binding.withType(objTy)); } - } else { - addNewBinding(binding.withSameSource(objTy, binding.Kind)); } } } diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index bc5d8513e1fc4..76cdb2c4eed1f 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 (auto varDecl = dyn_cast(DRE->getDecl())) { - if (CS.hasType(DRE)) { - LTI.collectedTypes.insert(CS.getType(DRE).getPointer()); - } - return Action::SkipNode(expr); - } - } - - // In the case of a function application, we would have already captured - // the return type during constraint generation, so there's no use in - // looking any further. - if (isa(expr) && - !(isa(expr) || isa(expr) || - isa(expr))) { - return Action::SkipNode(expr); - } - - if (auto *binaryExpr = dyn_cast(expr)) { - LTI.binaryExprs.push_back(binaryExpr); - } - - if (auto favoredType = CS.getFavoredType(expr)) { - LTI.collectedTypes.insert(favoredType); - - return Action::SkipNode(expr); - } - - // Optimize branches of a conditional expression separately. - if (auto IE = dyn_cast(expr)) { - CS.optimizeConstraints(IE->getCondExpr()); - CS.optimizeConstraints(IE->getThenExpr()); - CS.optimizeConstraints(IE->getElseExpr()); - return Action::SkipNode(expr); - } - - // For exprs of a tuple, avoid favoring. (We need to allow for cases like - // (Int, Int32).) - if (isa(expr)) { - return Action::SkipNode(expr); - } - - // Coercion exprs have a rigid type, so there's no use in gathering info - // about them. - if (auto *coercion = dyn_cast(expr)) { - // Let's not collect information about types initialized by - // coercions just like we don't for regular initializer calls, - // because that might lead to overly eager type variable merging. - if (!coercion->isLiteralInit()) - LTI.collectedTypes.insert(CS.getType(expr).getPointer()); - return Action::SkipNode(expr); - } - - // Don't walk into subscript expressions - to do so would risk factoring - // the index expression into edge contraction. (We don't want to do this - // if the index expression is a literal type that differs from the return - // type of the subscript operation.) - if (isa(expr) || isa(expr)) { - return Action::SkipNode(expr); - } - - // Don't walk into unresolved member expressions - we avoid merging type - // variables inside UnresolvedMemberExpr and those outside, since they - // should be allowed to behave independently in CS. - if (isa(expr)) { - return Action::SkipNode(expr); - } - - return Action::Continue(expr); - } - - /// Ignore statements. - PreWalkResult walkToStmtPre(Stmt *stmt) override { - return Action::SkipNode(stmt); - } - - /// Ignore declarations. - PreWalkAction walkToDeclPre(Decl *decl) override { - return Action::SkipNode(); - } - - /// Ignore patterns. - PreWalkResult walkToPatternPre(Pattern *pat) override { - return Action::SkipNode(pat); - } - - /// Ignore types. - PreWalkAction walkToTypeReprPre(TypeRepr *T) override { - return Action::SkipNode(); - } - }; - - /// For a given expression, given information that is global to the - /// expression, attempt to derive a favored type for it. - void computeFavoredTypeForExpr(Expr *expr, ConstraintSystem &CS) { - LinkedTypeInfo lti; - - expr->walk(LinkedExprAnalyzer(lti, CS)); - - // Check whether we can proceed with favoring. - if (llvm::any_of(lti.binaryExprs, [](const BinaryExpr *op) { - auto *ODRE = dyn_cast(op->getFn()); - if (!ODRE) - return false; - - // Attempting to favor based on operand types is wrong for - // nil-coalescing operator. - auto identifier = ODRE->getDecls().front()->getBaseIdentifier(); - return identifier.isNilCoalescingOperator(); - })) { - return; - } - - if (lti.collectedTypes.size() == 1) { - // TODO: Compute the BCT. - - // It's only useful to favor the type instead of - // binding it directly to arguments/result types, - // which means in case it has been miscalculated - // solver can still make progress. - auto favoredTy = (*lti.collectedTypes.begin())->getWithoutSpecifierType(); - CS.setFavoredType(expr, favoredTy.getPointer()); - - // If we have a chain of identical binop expressions with homogeneous - // argument types, we can directly simplify the associated constraint - // graph. - auto simplifyBinOpExprTyVars = [&]() { - // Don't attempt to do linking if there are - // literals intermingled with other inferred types. - if (lti.hasLiteral) - return; - - for (auto binExp1 : lti.binaryExprs) { - for (auto binExp2 : lti.binaryExprs) { - if (binExp1 == binExp2) - continue; - - auto fnTy1 = CS.getType(binExp1)->getAs(); - auto fnTy2 = CS.getType(binExp2)->getAs(); - - if (!(fnTy1 && fnTy2)) - return; - - auto ODR1 = dyn_cast(binExp1->getFn()); - auto ODR2 = dyn_cast(binExp2->getFn()); - - if (!(ODR1 && ODR2)) - return; - - // TODO: We currently limit this optimization to known arithmetic - // operators, but we should be able to broaden this out to - // logical operators as well. - if (!isArithmeticOperatorDecl(ODR1->getDecls()[0])) - return; - - if (ODR1->getDecls()[0]->getBaseName() != - ODR2->getDecls()[0]->getBaseName()) - return; - - // All things equal, we can merge the tyvars for the function - // types. - auto rep1 = CS.getRepresentative(fnTy1); - auto rep2 = CS.getRepresentative(fnTy2); - - if (rep1 != rep2) { - CS.mergeEquivalenceClasses(rep1, rep2, - /*updateWorkList*/ false); - } - - auto odTy1 = CS.getType(ODR1)->getAs(); - auto odTy2 = CS.getType(ODR2)->getAs(); - - if (odTy1 && odTy2) { - auto odRep1 = CS.getRepresentative(odTy1); - auto odRep2 = CS.getRepresentative(odTy2); - - // Since we'll be choosing the same overload, we can merge - // the overload tyvar as well. - if (odRep1 != odRep2) - CS.mergeEquivalenceClasses(odRep1, odRep2, - /*updateWorkList*/ false); - } - } - } - }; - - simplifyBinOpExprTyVars(); - } - } - - /// Determine whether the given parameter type and argument should be - /// "favored" because they match exactly. - bool isFavoredParamAndArg(ConstraintSystem &CS, Type paramTy, Type argTy, - Type otherArgTy = Type()) { - // Determine the argument type. - argTy = argTy->getWithoutSpecifierType(); - - // Do the types match exactly? - if (paramTy->isEqual(argTy)) - return true; - - // Don't favor narrowing conversions. - if (argTy->isDouble() && paramTy->isCGFloat()) - return false; - - llvm::SmallSetVector literalProtos; - if (auto argTypeVar = argTy->getAs()) { - auto constraints = CS.getConstraintGraph().gatherConstraints( - argTypeVar, ConstraintGraph::GatheringKind::EquivalenceClass, - [](Constraint *constraint) { - return constraint->getKind() == ConstraintKind::LiteralConformsTo; - }); - - for (auto constraint : constraints) { - literalProtos.insert(constraint->getProtocol()); - } - } - - // Dig out the second argument type. - if (otherArgTy) - otherArgTy = otherArgTy->getWithoutSpecifierType(); - - for (auto literalProto : literalProtos) { - // If there is another, concrete argument, check whether it's type - // conforms to the literal protocol and test against it directly. - // This helps to avoid 'widening' the favored type to the default type for - // the literal. - if (otherArgTy && otherArgTy->getAnyNominal()) { - if (otherArgTy->isEqual(paramTy) && - CS.lookupConformance(otherArgTy, literalProto)) { - return true; - } - } else if (Type defaultType = - TypeChecker::getDefaultType(literalProto, CS.DC)) { - // If there is a default type for the literal protocol, check whether - // it is the same as the parameter type. - // Check whether there is a default type to compare against. - if (paramTy->isEqual(defaultType) || - (defaultType->isDouble() && paramTy->isCGFloat())) - return true; - } - } - - return false; - } - - /// Favor certain overloads in a call based on some basic analysis - /// of the overload set and call arguments. - /// - /// \param expr The application. - /// \param isFavored Determine whether the given overload is favored, passing - /// it the "effective" overload type when it's being called. - /// \param mustConsider If provided, a function to detect the presence of - /// overloads which inhibit any overload from being favored. - void favorCallOverloads(ApplyExpr *expr, - ConstraintSystem &CS, - llvm::function_ref isFavored, - std::function - mustConsider = nullptr) { - // Find the type variable associated with the function, if any. - auto tyvarType = CS.getType(expr->getFn())->getAs(); - if (!tyvarType || CS.getFixedType(tyvarType)) - return; - - // This type variable is only currently associated with the function - // being applied, and the only constraint attached to it should - // be the disjunction constraint for the overload group. - auto disjunction = CS.getUnboundBindOverloadDisjunction(tyvarType); - if (!disjunction) - return; - - // Find the favored constraints and mark them. - SmallVector newlyFavoredConstraints; - unsigned numFavoredConstraints = 0; - Constraint *firstFavored = nullptr; - for (auto constraint : disjunction->getNestedConstraints()) { - auto *decl = constraint->getOverloadChoice().getDeclOrNull(); - if (!decl) - continue; - - if (mustConsider && mustConsider(decl)) { - // Roll back any constraints we favored. - for (auto favored : newlyFavoredConstraints) - favored->setFavored(false); - - return; - } - - Type overloadType = CS.getEffectiveOverloadType( - constraint->getLocator(), constraint->getOverloadChoice(), - /*allowMembers=*/true, CS.DC); - if (!overloadType) - continue; - - if (!CS.isDeclUnavailable(decl, constraint->getLocator()) && - !decl->getAttrs().hasAttribute() && - isFavored(decl, overloadType)) { - // If we might need to roll back the favored constraints, keep - // track of those we are favoring. - if (mustConsider && !constraint->isFavored()) - newlyFavoredConstraints.push_back(constraint); - - constraint->setFavored(); - ++numFavoredConstraints; - if (!firstFavored) - firstFavored = constraint; - } - } - - // If there was one favored constraint, set the favored type based on its - // result type. - if (numFavoredConstraints == 1) { - auto overloadChoice = firstFavored->getOverloadChoice(); - auto overloadType = CS.getEffectiveOverloadType( - firstFavored->getLocator(), overloadChoice, /*allowMembers=*/true, - CS.DC); - auto resultType = overloadType->castTo()->getResult(); - if (!resultType->hasTypeParameter()) - CS.setFavoredType(expr, resultType.getPointer()); - } - } - - /// Return a pair, containing the total parameter count of a function, coupled - /// with the number of non-default parameters. - std::pair getParamCount(ValueDecl *VD) { - auto fTy = VD->getInterfaceType()->castTo(); - - size_t nOperands = fTy->getParams().size(); - size_t nNoDefault = 0; - - if (auto AFD = dyn_cast(VD)) { - assert(!AFD->hasImplicitSelfDecl()); - for (auto param : *AFD->getParameters()) { - if (!param->isDefaultArgument()) - ++nNoDefault; - } - } else { - nNoDefault = nOperands; - } - - return { nOperands, nNoDefault }; - } - - bool hasContextuallyFavorableResultType(AnyFunctionType *choice, - Type contextualTy) { - // No restrictions of what result could be. - if (!contextualTy) - return true; - - auto resultTy = choice->getResult(); - // Result type of the call matches expected contextual type. - return contextualTy->isEqual(resultTy); - } - - /// Favor unary operator constraints where we have exact matches - /// for the operand and contextual type. - void favorMatchingUnaryOperators(ApplyExpr *expr, - ConstraintSystem &CS) { - auto *unaryArg = expr->getArgs()->getUnaryExpr(); - assert(unaryArg); - - // Determine whether the given declaration is favored. - auto isFavoredDecl = [&](ValueDecl *value, Type type) -> bool { - auto fnTy = type->getAs(); - if (!fnTy) - return false; - - auto params = fnTy->getParams(); - if (params.size() != 1) - return false; - - auto paramTy = params[0].getPlainType(); - auto argTy = CS.getType(unaryArg); - - // There are no CGFloat overloads on some of the unary operators, so - // in order to preserve current behavior, let's not favor overloads - // which would result in conversion from CGFloat to Double; otherwise - // it would lead to ambiguities. - if (argTy->isCGFloat() && paramTy->isDouble()) - return false; - - return isFavoredParamAndArg(CS, paramTy, argTy) && - hasContextuallyFavorableResultType( - fnTy, - CS.getContextualType(expr, /*forConstraint=*/false)); - }; - - favorCallOverloads(expr, CS, isFavoredDecl); - } - - void favorMatchingOverloadExprs(ApplyExpr *expr, - ConstraintSystem &CS) { - // Find the argument type. - size_t nArgs = expr->getArgs()->size(); - auto fnExpr = expr->getFn(); - - auto mustConsiderVariadicGenericOverloads = [&](ValueDecl *overload) { - if (overload->getAttrs().hasAttribute()) - return false; - - auto genericContext = overload->getAsGenericContext(); - if (!genericContext) - return false; - - auto *GPL = genericContext->getGenericParams(); - if (!GPL) - return false; - - return llvm::any_of(GPL->getParams(), - [&](const GenericTypeParamDecl *GP) { - return GP->isParameterPack(); - }); - }; - - // Check to ensure that we have an OverloadedDeclRef, and that we're not - // favoring multiple overload constraints. (Otherwise, in this case - // favoring is useless. - if (auto ODR = dyn_cast(fnExpr)) { - bool haveMultipleApplicableOverloads = false; - - for (auto VD : ODR->getDecls()) { - if (VD->getInterfaceType()->is()) { - auto nParams = getParamCount(VD); - - if (nArgs == nParams.first) { - if (haveMultipleApplicableOverloads) { - return; - } else { - haveMultipleApplicableOverloads = true; - } - } - } - } - - // Determine whether the given declaration is favored. - auto isFavoredDecl = [&](ValueDecl *value, Type type) -> bool { - // We want to consider all options for calls that might contain the code - // completion location, as missing arguments after the completion - // location are valid (since it might be that they just haven't been - // written yet). - if (CS.isForCodeCompletion()) - return false; - - if (!type->is()) - return false; - - auto paramCount = getParamCount(value); - - return nArgs == paramCount.first || - nArgs == paramCount.second; - }; - - favorCallOverloads(expr, CS, isFavoredDecl, - mustConsiderVariadicGenericOverloads); - } - - // We only currently perform favoring for unary args. - auto *unaryArg = expr->getArgs()->getUnlabeledUnaryExpr(); - if (!unaryArg) - return; - - if (auto favoredTy = CS.getFavoredType(unaryArg)) { - // Determine whether the given declaration is favored. - auto isFavoredDecl = [&](ValueDecl *value, Type type) -> bool { - auto fnTy = type->getAs(); - if (!fnTy || fnTy->getParams().size() != 1) - return false; - - return favoredTy->isEqual(fnTy->getParams()[0].getPlainType()); - }; - - // This is a hack to ensure we always consider the protocol requirement - // itself when calling something that has a default implementation in an - // extension. Otherwise, the extension method might be favored if we're - // inside an extension context, since any archetypes in the parameter - // list could match exactly. - auto mustConsider = [&](ValueDecl *value) -> bool { - return isa(value->getDeclContext()) || - mustConsiderVariadicGenericOverloads(value); - }; - - favorCallOverloads(expr, CS, isFavoredDecl, mustConsider); - } - } - - /// Favor binary operator constraints where we have exact matches - /// for the operands and contextual type. - void favorMatchingBinaryOperators(ApplyExpr *expr, ConstraintSystem &CS) { - // If we're generating constraints for a binary operator application, - // there are two special situations to consider: - // 1. If the type checker has any newly created functions with the - // operator's name. If it does, the overloads were created after the - // associated overloaded id expression was created, and we'll need to - // add a new disjunction constraint for the new set of overloads. - // 2. If any component argument expressions (nested or otherwise) are - // literals, we can favor operator overloads whose argument types are - // identical to the literal type, or whose return types are identical - // to any contextual type associated with the application expression. - - // Find the argument types. - auto *args = expr->getArgs(); - auto *lhs = args->getExpr(0); - auto *rhs = args->getExpr(1); - - auto firstArgTy = CS.getType(lhs); - auto secondArgTy = CS.getType(rhs); - - auto isOptionalWithMatchingObjectType = [](Type optional, - Type object) -> bool { - if (auto objTy = optional->getRValueType()->getOptionalObjectType()) - return objTy->getRValueType()->isEqual(object->getRValueType()); - - return false; - }; - - auto isPotentialForcingOpportunity = [&](Type first, Type second) -> bool { - return isOptionalWithMatchingObjectType(first, second) || - isOptionalWithMatchingObjectType(second, first); - }; - - // Determine whether the given declaration is favored. - auto isFavoredDecl = [&](ValueDecl *value, Type type) -> bool { - auto fnTy = type->getAs(); - if (!fnTy) - return false; - - auto firstFavoredTy = CS.getFavoredType(lhs); - auto secondFavoredTy = CS.getFavoredType(rhs); - - auto favoredExprTy = CS.getFavoredType(expr); - - if (isArithmeticOperatorDecl(value)) { - // If the parent has been favored on the way down, propagate that - // information to its children. - // TODO: This is only valid for arithmetic expressions. - if (!firstFavoredTy) { - CS.setFavoredType(lhs, favoredExprTy); - firstFavoredTy = favoredExprTy; - } - - if (!secondFavoredTy) { - CS.setFavoredType(rhs, favoredExprTy); - secondFavoredTy = favoredExprTy; - } - } - - auto params = fnTy->getParams(); - if (params.size() != 2) - return false; - - auto firstParamTy = params[0].getOldType(); - auto secondParamTy = params[1].getOldType(); - - auto contextualTy = CS.getContextualType(expr, /*forConstraint=*/false); - - // Avoid favoring overloads that would require narrowing conversion - // to match the arguments. - { - if (firstArgTy->isDouble() && firstParamTy->isCGFloat()) - return false; - - if (secondArgTy->isDouble() && secondParamTy->isCGFloat()) - return false; - } - - return (isFavoredParamAndArg(CS, firstParamTy, firstArgTy, secondArgTy) || - isFavoredParamAndArg(CS, secondParamTy, secondArgTy, - firstArgTy)) && - firstParamTy->isEqual(secondParamTy) && - !isPotentialForcingOpportunity(firstArgTy, secondArgTy) && - hasContextuallyFavorableResultType(fnTy, contextualTy); - }; - - favorCallOverloads(expr, CS, isFavoredDecl); - } - /// If \p expr is a call and that call contains the code completion token, /// add the expressions of all arguments after the code completion token to /// \p ignoredArguments. @@ -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) { @@ -1100,18 +348,6 @@ namespace { if (isLValueBase) outputTy = LValueType::get(outputTy); } - } else if (auto dictTy = CS.isDictionaryType(baseObjTy)) { - auto keyTy = dictTy->first; - auto valueTy = dictTy->second; - - if (argList->isUnlabeledUnary()) { - auto argTy = CS.getType(argList->getExpr(0)); - if (isFavoredParamAndArg(CS, keyTy, argTy)) { - outputTy = OptionalType::get(valueTy); - if (isLValueBase) - outputTy = LValueType::get(outputTy); - } - } } } @@ -1167,7 +403,6 @@ namespace { Type fixedOutputType = CS.getFixedTypeRecursive(outputTy, /*wantRValue=*/false); if (!fixedOutputType->isTypeVariableOrMember()) { - CS.setFavoredType(anchor, fixedOutputType.getPointer()); outputTy = fixedOutputType; } @@ -1571,11 +806,6 @@ namespace { return eltType; } } - - if (!knownType->hasPlaceholder()) { - // Set the favored type for this expression to the known type. - CS.setFavoredType(E, knownType.getPointer()); - } } } @@ -2054,9 +1284,6 @@ namespace { CS.getASTContext()); } - if (auto favoredTy = CS.getFavoredType(expr->getSubExpr())) { - CS.setFavoredType(expr, favoredTy); - } return CS.getType(expr->getSubExpr()); } @@ -3331,7 +2558,6 @@ namespace { Type fixedType = CS.getFixedTypeRecursive(resultType, /*wantRvalue=*/true); if (!fixedType->isTypeVariableOrMember()) { - CS.setFavoredType(expr, fixedType.getPointer()); resultType = fixedType; } @@ -4385,12 +3611,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( @@ -5141,26 +4362,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..afaa7aaa9acd2 --- /dev/null +++ b/lib/Sema/CSOptimizer.cpp @@ -0,0 +1,1240 @@ +//===--- CSOptimizer.cpp - Constraint Optimizer ---------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 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/ExistentialLayout.h" +#include "swift/AST/GenericSignature.h" +#include "swift/Basic/OptionSet.h" +#include "swift/Sema/ConstraintGraph.h" +#include "swift/Sema/ConstraintSystem.h" +#include "llvm/ADT/BitVector.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/TinyPtrVector.h" +#include "llvm/Support/SaveAndRestore.h" +#include "llvm/Support/raw_ostream.h" +#include +#include + +using namespace swift; +using namespace constraints; + +namespace { + +struct DisjunctionInfo { + /// The score of the disjunction is the highest score from its choices. + /// If the score is nullopt it means that the disjunction is not optimizable. + std::optional Score; + /// The highest scoring choices that could be favored when disjunction + /// is attempted. + llvm::TinyPtrVector FavoredChoices; + + DisjunctionInfo() = default; + DisjunctionInfo(double score, ArrayRef favoredChoices = {}) + : Score(score), FavoredChoices(favoredChoices) {} +}; + +// TODO: both `isIntegerType` and `isFloatType` should be available on Type +// as `isStdlib{Integer, Float}Type`. + +static bool isIntegerType(Type type) { + return type->isInt() || type->isInt8() || type->isInt16() || + type->isInt32() || type->isInt64() || type->isUInt() || + type->isUInt8() || type->isUInt16() || type->isUInt32() || + type->isUInt64(); +} + +static bool isFloatType(Type type) { + return type->isFloat() || type->isDouble() || type->isFloat80(); +} + +static bool isSupportedOperator(Constraint *disjunction) { + if (!isOperatorDisjunction(disjunction)) + return false; + + auto choices = disjunction->getNestedConstraints(); + auto *decl = getOverloadChoiceDecl(choices.front()); + + auto name = decl->getBaseIdentifier(); + if (name.isArithmeticOperator() || name.isStandardComparisonOperator() || + name.isBitwiseOperator()) { + return true; + } + + // Operators like &<<, &>>, &+, .== etc. + if (llvm::any_of(choices, [](Constraint *choice) { + return isSIMDOperator(getOverloadChoiceDecl(choice)); + })) { + return true; + } + + return false; +} + +static bool isSupportedSpecialConstructor(ConstructorDecl *ctor) { + if (auto *selfDecl = ctor->getImplicitSelfDecl()) { + auto selfTy = selfDecl->getInterfaceType(); + /// Support `Int*`, `Float*` and `Double` initializers since their generic + /// overloads are not too complicated. + return selfTy && (isIntegerType(selfTy) || isFloatType(selfTy)); + } + return false; +} + +static bool isStandardComparisonOperator(ValueDecl *decl) { + return decl->isOperator() && + decl->getBaseIdentifier().isStandardComparisonOperator(); +} + +static bool isArithmeticOperator(ValueDecl *decl) { + return decl->isOperator() && decl->getBaseIdentifier().isArithmeticOperator(); +} + +static bool isSupportedDisjunction(Constraint *disjunction) { + auto choices = disjunction->getNestedConstraints(); + + if (isSupportedOperator(disjunction)) + return true; + + if (auto *ctor = dyn_cast_or_null( + getOverloadChoiceDecl(choices.front()))) { + if (isSupportedSpecialConstructor(ctor)) + return true; + } + + // Non-operator disjunctions are supported only if they don't + // have any generic choices. + return llvm::all_of(choices, [&](Constraint *choice) { + if (choice->getKind() != ConstraintKind::BindOverload) + return false; + + if (auto *decl = getOverloadChoiceDecl(choice)) + return decl->getInterfaceType()->is(); + + return false; + }); +} + +NullablePtr getApplicableFnConstraint(ConstraintGraph &CG, + Constraint *disjunction) { + auto *boundVar = disjunction->getNestedConstraints()[0] + ->getFirstType() + ->getAs(); + if (!boundVar) + return nullptr; + + auto constraints = CG.gatherConstraints( + boundVar, ConstraintGraph::GatheringKind::EquivalenceClass, + [](Constraint *constraint) { + return constraint->getKind() == ConstraintKind::ApplicableFunction; + }); + + if (constraints.size() != 1) + return nullptr; + + auto *applicableFn = constraints.front(); + // Unapplied disjunction could appear as a argument to applicable function, + // we are not interested in that. + return applicableFn->getSecondType()->isEqual(boundVar) ? applicableFn + : nullptr; +} + +void forEachDisjunctionChoice( + ConstraintSystem &cs, Constraint *disjunction, + llvm::function_ref + callback) { + for (auto constraint : disjunction->getNestedConstraints()) { + if (constraint->isDisabled()) + continue; + + if (constraint->getKind() != ConstraintKind::BindOverload) + continue; + + auto choice = constraint->getOverloadChoice(); + auto *decl = choice.getDeclOrNull(); + if (!decl) + continue; + + // If disjunction choice is unavailable or disfavored we cannot + // do anything with it. + if (decl->getAttrs().hasAttribute() || + cs.isDeclUnavailable(decl, disjunction->getLocator())) + continue; + + Type overloadType = + cs.getEffectiveOverloadType(disjunction->getLocator(), choice, + /*allowMembers=*/true, cs.DC); + + if (!overloadType || !overloadType->is()) + continue; + + callback(constraint, decl, overloadType->castTo()); + } +} + +static OverloadedDeclRefExpr *isOverloadedDeclRef(Constraint *disjunction) { + assert(disjunction->getKind() == ConstraintKind::Disjunction); + + auto *locator = disjunction->getLocator(); + if (locator->getPath().empty()) + return getAsExpr(locator->getAnchor()); + return nullptr; +} + +static unsigned numOverloadChoicesMatchingOnArity(OverloadedDeclRefExpr *ODRE, + ArgumentList *arguments) { + return llvm::count_if(ODRE->getDecls(), [&arguments](auto *choice) { + if (auto *paramList = getParameterList(choice)) + return arguments->size() == paramList->size(); + return false; + }); +} + +/// This maintains an "old hack" behavior where overloads of some +/// `OverloadedDeclRef` calls were favored purely based on number of +/// argument and (non-defaulted) parameters matching. +static void findFavoredChoicesBasedOnArity( + ConstraintSystem &cs, Constraint *disjunction, ArgumentList *argumentList, + llvm::function_ref favoredChoice) { + auto *ODRE = isOverloadedDeclRef(disjunction); + if (!ODRE) + return; + + if (numOverloadChoicesMatchingOnArity(ODRE, argumentList) > 1) + return; + + auto isVariadicGenericOverload = [&](ValueDecl *choice) { + auto genericContext = choice->getAsGenericContext(); + if (!genericContext) + return false; + + auto *GPL = genericContext->getGenericParams(); + if (!GPL) + return false; + + return llvm::any_of(GPL->getParams(), [&](const GenericTypeParamDecl *GP) { + return GP->isParameterPack(); + }); + }; + + bool hasVariadicGenerics = false; + SmallVector favored; + + forEachDisjunctionChoice( + cs, disjunction, + [&](Constraint *choice, ValueDecl *decl, FunctionType *overloadType) { + if (isVariadicGenericOverload(decl)) + hasVariadicGenerics = true; + + if (overloadType->getNumParams() == argumentList->size() || + llvm::count_if(*getParameterList(decl), [](auto *param) { + return !param->isDefaultArgument(); + }) == argumentList->size()) + favored.push_back(choice); + }); + + if (hasVariadicGenerics) + return; + + for (auto *choice : favored) + favoredChoice(choice); +} + +} // end anonymous namespace + +/// Given a set of disjunctions, attempt to determine +/// favored choices in the current context. +static void determineBestChoicesInContext( + ConstraintSystem &cs, SmallVectorImpl &disjunctions, + llvm::DenseMap &result) { + double bestOverallScore = 0.0; + + auto recordResult = [&bestOverallScore, &result](Constraint *disjunction, + DisjunctionInfo &&info) { + bestOverallScore = std::max(bestOverallScore, info.Score.value_or(0)); + result.try_emplace(disjunction, info); + }; + + for (auto *disjunction : disjunctions) { + // If this is a compiler synthesized disjunction, mark it as supported + // and record all of the previously favored choices. Such disjunctions + // include - explicit coercions, IUO references,injected implicit + // initializers for CGFloat<->Double conversions and restrictions with + // multiple choices. + if (disjunction->countFavoredNestedConstraints() > 0) { + DisjunctionInfo info(/*score=*/2.0); + llvm::copy_if(disjunction->getNestedConstraints(), + std::back_inserter(info.FavoredChoices), + [](Constraint *choice) { return choice->isFavored(); }); + recordResult(disjunction, std::move(info)); + continue; + } + + auto applicableFn = + getApplicableFnConstraint(cs.getConstraintGraph(), disjunction); + + if (applicableFn.isNull()) { + auto *locator = disjunction->getLocator(); + if (auto expr = getAsExpr(locator->getAnchor())) { + if (auto *parentExpr = cs.getParentExpr(expr)) { + // 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: + recordResult(disjunction, {/*score=*/1.0}); + continue; + + default: + break; + } + } + } + + continue; + } + + auto argFuncType = + applicableFn.get()->getFirstType()->getAs(); + + auto argumentList = cs.getArgumentList(applicableFn.get()->getLocator()); + if (!argumentList) + return; + + for (const auto &argument : *argumentList) { + if (auto *expr = argument.getExpr()) { + // Directly `<#...#>` or has one inside. + if (isa(expr) || + cs.containsIDEInspectionTarget(expr)) + return; + } + } + + // This maintains an "old hack" behavior where overloads + // of `OverloadedDeclRef` calls were favored purely + // based on arity of arguments and parameters matching. + { + llvm::TinyPtrVector favoredChoices; + findFavoredChoicesBasedOnArity(cs, disjunction, argumentList, + [&favoredChoices](Constraint *choice) { + favoredChoices.push_back(choice); + }); + + if (!favoredChoices.empty()) { + recordResult(disjunction, {/*score=*/0.01, favoredChoices}); + continue; + } + } + + if (!isSupportedDisjunction(disjunction)) + continue; + + SmallVector argsWithLabels; + { + argsWithLabels.append(argFuncType->getParams().begin(), + argFuncType->getParams().end()); + FunctionType::relabelParams(argsWithLabels, argumentList); + } + + SmallVector, 2>, 2> + candidateArgumentTypes; + candidateArgumentTypes.resize(argFuncType->getNumParams()); + + llvm::TinyPtrVector resultTypes; + + for (unsigned i = 0, n = argFuncType->getNumParams(); i != n; ++i) { + const auto ¶m = argFuncType->getParams()[i]; + auto argType = cs.simplifyType(param.getPlainType()); + + SmallVector, 2> types; + if (auto *typeVar = argType->getAs()) { + auto bindingSet = cs.getBindingsFor(typeVar, /*finalize=*/true); + + for (const auto &binding : bindingSet.Bindings) { + types.push_back({binding.BindingType, /*fromLiteral=*/false}); + } + + for (const auto &literal : bindingSet.Literals) { + if (literal.second.hasDefaultType()) { + // Add primary default type + types.push_back( + {literal.second.getDefaultType(), /*fromLiteral=*/true}); + } + } + + // Helps situations like `1 + {Double, CGFloat}(...)` by inferring + // a type for the second operand of `+` based on a type being constructed. + // + // Currently limited to Double and CGFloat only since we need to + // support implicit `Double<->CGFloat` conversion. + if (typeVar->getImpl().isFunctionResult() && + isOperatorDisjunction(disjunction)) { + auto resultLoc = typeVar->getImpl().getLocator(); + if (auto *call = getAsExpr(resultLoc->getAnchor())) { + if (auto *typeExpr = dyn_cast(call->getFn())) { + auto instanceTy = cs.getType(typeExpr)->getMetatypeInstanceType(); + if (instanceTy->isDouble() || instanceTy->isCGFloat()) + types.push_back({instanceTy, /*fromLiteral=*/false}); + } + } + } + } else { + types.push_back({argType, /*fromLiteral=*/false}); + } + + candidateArgumentTypes[i].append(types); + } + + auto resultType = cs.simplifyType(argFuncType->getResult()); + if (auto *typeVar = resultType->getAs()) { + auto bindingSet = cs.getBindingsFor(typeVar, /*finalize=*/true); + + for (const auto &binding : bindingSet.Bindings) { + resultTypes.push_back(binding.BindingType); + } + } else { + resultTypes.push_back(resultType); + } + + // Determine whether all of the argument candidates are inferred from literals. + // This information is going to be used later on when we need to decide how to + // score a matching choice. + bool onlyLiteralCandidates = + argFuncType->getNumParams() > 0 && + llvm::none_of( + indices(argFuncType->getParams()), [&](const unsigned argIdx) { + auto &candidates = candidateArgumentTypes[argIdx]; + return llvm::any_of(candidates, [&](const auto &candidate) { + return !candidate.second; + }); + }); + + // Match arguments to the given overload choice. + auto matchArguments = [&](OverloadChoice choice, FunctionType *overloadType) + -> std::optional { + auto *decl = choice.getDeclOrNull(); + assert(decl); + + auto hasAppliedSelf = + decl->hasCurriedSelf() && + doesMemberRefApplyCurriedSelf(choice.getBaseType(), decl); + + ParameterListInfo paramListInfo(overloadType->getParams(), decl, + hasAppliedSelf); + + MatchCallArgumentListener listener; + return matchCallArguments(argsWithLabels, overloadType->getParams(), + paramListInfo, + argumentList->getFirstTrailingClosureIndex(), + /*allow fixes*/ false, listener, std::nullopt); + }; + + // Determine whether the candidate type is a subclass of the superclass + // type. + std::function isSubclassOf = [&](Type candidateType, + Type superclassType) { + // Conversion from a concrete type to its existential value. + if (superclassType->isExistentialType() && !superclassType->isAny()) { + auto layout = superclassType->getExistentialLayout(); + + if (auto layoutConstraint = layout.getLayoutConstraint()) { + if (layoutConstraint->isClass() && + !(candidateType->isClassExistentialType() || + candidateType->mayHaveSuperclass())) + return false; + } + + if (layout.explicitSuperclass && + !isSubclassOf(candidateType, layout.explicitSuperclass)) + return false; + + return llvm::all_of(layout.getProtocols(), [&](ProtocolDecl *P) { + if (auto superclass = P->getSuperclassDecl()) { + if (!isSubclassOf(candidateType, + superclass->getDeclaredInterfaceType())) + return false; + } + + return bool(TypeChecker::containsProtocol(candidateType, P, + /*allowMissing=*/false)); + }); + } + + auto *subclassDecl = candidateType->getClassOrBoundGenericClass(); + auto *superclassDecl = superclassType->getClassOrBoundGenericClass(); + + if (!(subclassDecl && superclassDecl)) + return false; + + return superclassDecl->isSuperclassOf(subclassDecl); + }; + + enum class MatchFlag { + OnParam = 0x01, + Literal = 0x02, + ExactOnly = 0x04, + DisableCGFloatDoubleConversion = 0x08, + }; + + using MatchOptions = OptionSet; + + // Perform a limited set of checks to determine whether the candidate + // could possibly match the parameter type: + // + // - Equality + // - Protocol conformance(s) + // - Optional injection + // - Superclass conversion + // - Array-to-pointer conversion + // - Value to existential conversion + // - Exact match on top-level types + // + // In situations when it's not possible to determine whether a candidate + // type matches a parameter type (i.e. when partially resolved generic + // types are matched) this function is going to produce \c std::nullopt + // instead of `0` that indicates "not a match". + std::function(GenericSignature, Type, Type, + MatchOptions)> + scoreCandidateMatch = + [&](GenericSignature genericSig, 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)) + return areEqual(candidateType, paramType) ? 1 : 0; + + // Exact match between candidate and parameter types. + if (areEqual(candidateType, paramType)) { + return options.contains(MatchFlag::Literal) ? 0.3 : 1; + } + + if (options.contains(MatchFlag::Literal)) { + // Integer and floating-point literals can match any parameter + // type that conforms to `ExpressibleBy{Integer, Float}Literal` + // protocol but since that would constitute a non-default binding + // the score has to be slightly lowered. + if (!paramType->hasTypeParameter()) { + if (candidateType->isInt() && + TypeChecker::conformsToKnownProtocol( + paramType, KnownProtocolKind::ExpressibleByIntegerLiteral)) + return paramType->isDouble() ? 0.2 : 0.3; + + if (candidateType->isDouble() && + TypeChecker::conformsToKnownProtocol( + paramType, KnownProtocolKind::ExpressibleByFloatLiteral)) + return 0.3; + } + + return 0; + } + + // Check whether match would require optional injection. + { + SmallVector candidateOptionals; + SmallVector paramOptionals; + + candidateType = + candidateType->lookThroughAllOptionalTypes(candidateOptionals); + paramType = paramType->lookThroughAllOptionalTypes(paramOptionals); + + if (!candidateOptionals.empty() || !paramOptionals.empty()) { + if (paramOptionals.size() >= candidateOptionals.size()) { + auto score = scoreCandidateMatch(genericSig, candidateType, + paramType, options); + // Injection lowers the score slightly to comply with + // old behavior where exact matches on operator parameter + // types were always preferred. + return score == 1 && isOperatorDisjunction(disjunction) ? 0.9 + : score; + } + + // Optionality mismatch. + return 0; + } + } + + // Candidate could be converted to a superclass. + if (isSubclassOf(candidateType, paramType)) + return 1; + + // Possible Array -> Unsafe*Pointer conversion. + if (options.contains(MatchFlag::OnParam)) { + if (candidateType->isArrayType() && + paramType->getAnyPointerElementType()) + return 1; + } + + // If both argument and parameter are tuples of the same arity, + // it's a match. + { + if (auto *candidateTuple = candidateType->getAs()) { + auto *paramTuple = paramType->getAs(); + if (paramTuple && + candidateTuple->getNumElements() == paramTuple->getNumElements()) + return 1; + } + } + + // Check protocol requirement(s) if this parameter is a + // generic parameter type. + if (genericSig && paramType->isTypeParameter()) { + // Light-weight check if cases where `checkRequirements` is not + // applicable. + auto checkProtocolRequirementsOnly = [&]() -> double { + auto protocolRequirements = + genericSig->getRequiredProtocols(paramType); + if (llvm::all_of(protocolRequirements, [&](ProtocolDecl *protocol) { + return bool(cs.lookupConformance(candidateType, protocol)); + })) { + if (auto *GP = paramType->getAs()) { + auto *paramDecl = GP->getDecl(); + if (paramDecl && paramDecl->isOpaqueType()) + return 1.0; + } + return 0.7; + } + + return 0; + }; + + // If candidate is not fully resolved or is matched against a + // dependent member type (i.e. `Self.T`), let's check conformances + // only and lower the score. + if (candidateType->hasTypeVariable() || + paramType->is()) { + return checkProtocolRequirementsOnly(); + } + + // Cannot match anything but generic type parameters here. + if (!paramType->is()) + return std::nullopt; + + bool hasUnsatisfiableRequirements = false; + SmallVector requirements; + + for (const auto &requirement : genericSig.getRequirements()) { + if (hasUnsatisfiableRequirements) + break; + + llvm::SmallPtrSet toExamine; + + auto recordReferencesGenericParams = [&toExamine](Type type) { + type.visit([&toExamine](Type innerTy) { + if (auto *GP = innerTy->getAs()) + toExamine.insert(GP); + }); + }; + + recordReferencesGenericParams(requirement.getFirstType()); + + if (requirement.getKind() != RequirementKind::Layout) + recordReferencesGenericParams(requirement.getSecondType()); + + if (llvm::any_of(toExamine, [&](GenericTypeParamType *GP) { + return paramType->isEqual(GP); + })) { + requirements.push_back(requirement); + // If requirement mentions other generic parameters + // `checkRequirements` would because we don't have + // candidate substitutions for anything but the current + // parameter type. + hasUnsatisfiableRequirements |= toExamine.size() > 1; + } + } + + // If some of the requirements cannot be satisfied, because + // they reference other generic parameters, for example: + // ``, let's perform a + // light-weight check instead of skipping this overload choice. + if (hasUnsatisfiableRequirements) + return checkProtocolRequirementsOnly(); + + // If the candidate type is fully resolved, let's check all of + // the requirements that are associated with the corresponding + // parameter, if all of them are satisfied this candidate is + // an exact match. + auto result = checkRequirements( + requirements, + [¶mType, &candidateType](SubstitutableType *type) -> Type { + if (type->isEqual(paramType)) + return candidateType; + return ErrorType::get(type); + }, + SubstOptions(std::nullopt)); + + // Concrete operator overloads are always more preferable to + // generic ones if there are exact or subtype matches, for + // everything else the solver should try both concrete and + // generic and disambiguate during ranking. + if (result == CheckRequirementsResult::Success) + return isOperatorDisjunction(disjunction) ? 0.9 : 1.0; + + return 0; + } + + // Parameter is generic, let's check whether top-level + // types match i.e. Array as a parameter. + // + // This is slightly better than all of the conformances matching + // because the parameter is concrete and could split the graph. + if (paramType->hasTypeParameter()) { + auto *candidateDecl = candidateType->getAnyNominal(); + auto *paramDecl = paramType->getAnyNominal(); + + if (candidateDecl && paramDecl && candidateDecl == paramDecl) + return 0.8; + } + + return 0; + }; + + // The choice with the best score. + double bestScore = 0.0; + SmallVector, 2> favoredChoices; + + // Preserves old behavior where, for unary calls, the solver + // would not consider choices that didn't match on the number + // of parameters (regardless of defaults) and only exact + // matches were favored. + bool preserveFavoringOfUnlabeledUnaryArgument = false; + if (argumentList->isUnlabeledUnary()) { + auto ODRE = isOverloadedDeclRef(disjunction); + preserveFavoringOfUnlabeledUnaryArgument = + !ODRE || numOverloadChoicesMatchingOnArity(ODRE, argumentList) < 2; + } + + forEachDisjunctionChoice( + cs, disjunction, + [&](Constraint *choice, ValueDecl *decl, FunctionType *overloadType) { + GenericSignature genericSig; + { + if (auto *GF = dyn_cast(decl)) { + genericSig = GF->getGenericSignature(); + } else if (auto *SD = dyn_cast(decl)) { + genericSig = SD->getGenericSignature(); + } + } + + auto matchings = + matchArguments(choice->getOverloadChoice(), overloadType); + if (!matchings) + return; + + // If all of the arguments are literals, let's prioritize exact + // matches to filter out non-default literal bindings which otherwise + // could cause "over-favoring". + bool favorExactMatchesOnly = onlyLiteralCandidates; + + if (preserveFavoringOfUnlabeledUnaryArgument) { + // Old behavior completely disregarded the fact that some of + // the parameters could be defaulted. + if (overloadType->getNumParams() != 1) + return; + + favorExactMatchesOnly = true; + } + + // This is important for SIMD operators in particular because + // a lot of their overloads have same-type requires to a concrete + // type: `(_: SIMD*, ...) -> ...`. + if (genericSig) { + overloadType = overloadType->getReducedType(genericSig) + ->castTo(); + } + + double score = 0.0; + unsigned numDefaulted = 0; + for (unsigned paramIdx = 0, n = overloadType->getNumParams(); + paramIdx != n; ++paramIdx) { + const auto ¶m = overloadType->getParams()[paramIdx]; + + auto argIndices = matchings->parameterBindings[paramIdx]; + switch (argIndices.size()) { + case 0: + // Current parameter is defaulted, mark and continue. + ++numDefaulted; + continue; + + case 1: + // One-to-one match between argument and parameter. + break; + + default: + // Cannot deal with multiple possible matchings at the moment. + return; + } + + auto argIdx = argIndices.front(); + + // Looks like there is nothing know about the argument. + if (candidateArgumentTypes[argIdx].empty()) + continue; + + const auto paramFlags = param.getParameterFlags(); + + // If parameter is variadic we cannot compare because we don't know + // real arity. + if (paramFlags.isVariadic()) + continue; + + auto paramType = param.getPlainType(); + + // FIXME: Let's skip matching function types for now + // because they have special rules for e.g. Concurrency + // (around @Sendable) and @convention(c). + if (paramType->is()) + continue; + + // The idea here is to match the parameter type against + // all of the argument candidate types and pick the best + // match (i.e. exact equality one). + // + // If none of the candidates match exactly and they are + // all bound concrete types, we consider this is mismatch + // at this parameter position and remove the overload choice + // from consideration. + double bestCandidateScore = 0; + llvm::BitVector mismatches(candidateArgumentTypes[argIdx].size()); + + for (unsigned candidateIdx : + indices(candidateArgumentTypes[argIdx])) { + // If one of the candidates matched exactly there is no reason + // to continue checking. + if (bestCandidateScore == 1) + break; + + Type candidateType; + bool isLiteralDefault; + + std::tie(candidateType, isLiteralDefault) = + candidateArgumentTypes[argIdx][candidateIdx]; + + // `inout` parameter accepts only l-value argument. + if (paramFlags.isInOut() && !candidateType->is()) { + mismatches.set(candidateIdx); + continue; + } + + // The specifier only matters for `inout` check. + candidateType = candidateType->getWithoutSpecifierType(); + + MatchOptions options(MatchFlag::OnParam); + if (isLiteralDefault) + 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; + + auto candidateScore = scoreCandidateMatch( + genericSig, candidateType, paramType, options); + + if (!candidateScore) + continue; + + if (candidateScore > 0) { + bestCandidateScore = + std::max(bestCandidateScore, candidateScore.value()); + continue; + } + + // Only established arguments could be considered mismatches, + // literal default types should be regarded as holes if they + // didn't match. + if (!isLiteralDefault && !candidateType->hasTypeVariable()) + mismatches.set(candidateIdx); + } + + // If none of the candidates for this parameter matched, let's + // drop this overload from any further consideration. + if (mismatches.all()) + return; + + score += bestCandidateScore; + } + + // An overload whether all of the parameters are defaulted + // that's called without arguments. + if (numDefaulted == overloadType->getNumParams()) + return; + + // Average the score to avoid disfavoring disjunctions with fewer + // parameters. + score /= (overloadType->getNumParams() - numDefaulted); + + // Make sure that the score is uniform for all disjunction + // choices that match on literals only, this would make sure that + // in operator chains that consist purely of literals we'd + // always prefer outermost disjunction instead of innermost + // one. + // + // Preferring outer disjunction first works better in situations + // when contextual type for the whole chain becomes available at + // some point during solving at it would allow for faster pruning. + if (score > 0 && onlyLiteralCandidates) + score = 0.1; + + // If one of the result types matches exactly, that's a good + // indication that overload choice should be favored. + // + // If nothing is known about the arguments it's only safe to + // check result for operators (except to standard comparison + // ones that all have the same result type), regular + // functions/methods and especially initializers could end up + // with a lot of favored overloads because on the result type alone. + if (decl->isOperator() && !isStandardComparisonOperator(decl)) { + if (llvm::any_of(resultTypes, [&](const Type candidateResultTy) { + // Avoid increasing weight based on CGFloat result type + // match because that could require narrowing conversion + // in the arguments and that is always detrimental. + // + // For example, `has_CGFloat_param(1.0 + 2.0)` should use + // `+(_: Double, _: Double) -> Double` instead of + // `+(_: CGFloat, _: CGFloat) -> CGFloat` which would match + // parameter of `has_CGFloat_param` exactly but use a + // narrowing conversion for both literals. + if (candidateResultTy->lookThroughAllOptionalTypes() + ->isCGFloat()) + return false; + + return scoreCandidateMatch(genericSig, + overloadType->getResult(), + candidateResultTy, + /*options=*/{}) > 0; + })) { + score += 1.0; + } + } + + if (score > 0) { + // Nudge the score slightly to prefer concrete homogeneous + // arithmetic operators. + // + // This is an opportunistic optimization based on the operator + // use patterns where homogeneous operators are the most + // heavily used ones. + if (isArithmeticOperator(decl) && + overloadType->getNumParams() == 2) { + auto resultTy = overloadType->getResult(); + if (!resultTy->hasTypeParameter() && + llvm::all_of(overloadType->getParams(), + [&resultTy](const auto ¶m) { + return param.getPlainType()->isEqual(resultTy); + })) + score += 0.1; + } + + favoredChoices.push_back({choice, score}); + bestScore = std::max(bestScore, score); + } + }); + + if (cs.isDebugMode()) { + PrintOptions PO; + PO.PrintTypesForDebugging = true; + + llvm::errs().indent(cs.solverState->getCurrentIndent()) + << "<<< Disjunction " + << disjunction->getNestedConstraints()[0]->getFirstType()->getString( + PO) + << " with score " << bestScore << "\n"; + } + + bestOverallScore = std::max(bestOverallScore, bestScore); + + DisjunctionInfo info(/*score=*/bestScore); + + for (const auto &choice : favoredChoices) { + if (choice.second == bestScore) + info.FavoredChoices.push_back(choice.first); + } + + recordResult(disjunction, std::move(info)); + } + + if (cs.isDebugMode() && bestOverallScore > 0) { + PrintOptions PO; + PO.PrintTypesForDebugging = true; + + auto getLogger = [&](unsigned extraIndent = 0) -> llvm::raw_ostream & { + return llvm::errs().indent(cs.solverState->getCurrentIndent() + + extraIndent); + }; + + { + auto &log = getLogger(); + log << "(Optimizing disjunctions: ["; + + interleave( + disjunctions, + [&](const auto *disjunction) { + log << disjunction->getNestedConstraints()[0] + ->getFirstType() + ->getString(PO); + }, + [&]() { log << ", "; }); + + log << "]\n"; + } + + getLogger(/*extraIndent=*/4) + << "Best overall score = " << bestOverallScore << '\n'; + + for (auto *disjunction : disjunctions) { + auto &entry = result[disjunction]; + getLogger(/*extraIndent=*/4) + << "[Disjunction '" + << disjunction->getNestedConstraints()[0]->getFirstType()->getString( + PO) + << "' with score = " << entry.Score.value_or(0) << '\n'; + + for (const auto *choice : entry.FavoredChoices) { + auto &log = getLogger(/*extraIndent=*/6); + + log << "- "; + choice->print(log, &cs.getASTContext().SourceMgr); + log << '\n'; + } + + getLogger(/*extraIdent=*/4) << "]\n"; + } + + getLogger() << ")\n"; + } +} + +// Attempt to find a disjunction of bind constraints where all options +// in the disjunction are binding the same type variable. +// +// Prefer disjunctions where the bound type variable is also the +// right-hand side of a conversion constraint, since having a concrete +// type that we're converting to can make it possible to split the +// constraint system into multiple ones. +static Constraint * +selectBestBindingDisjunction(ConstraintSystem &cs, + SmallVectorImpl &disjunctions) { + + if (disjunctions.empty()) + return nullptr; + + auto getAsTypeVar = [&cs](Type type) { + return cs.simplifyType(type)->getRValueType()->getAs(); + }; + + Constraint *firstBindDisjunction = nullptr; + for (auto *disjunction : disjunctions) { + auto choices = disjunction->getNestedConstraints(); + assert(!choices.empty()); + + auto *choice = choices.front(); + if (choice->getKind() != ConstraintKind::Bind) + continue; + + // We can judge disjunction based on the single choice + // because all of choices (of bind overload set) should + // have the same left-hand side. + // Only do this for simple type variable bindings, not for + // bindings like: ($T1) -> $T2 bind String -> Int + auto *typeVar = getAsTypeVar(choice->getFirstType()); + if (!typeVar) + continue; + + if (!firstBindDisjunction) + firstBindDisjunction = disjunction; + + auto constraints = cs.getConstraintGraph().gatherConstraints( + typeVar, ConstraintGraph::GatheringKind::EquivalenceClass, + [](Constraint *constraint) { + return constraint->getKind() == ConstraintKind::Conversion; + }); + + for (auto *constraint : constraints) { + if (typeVar == getAsTypeVar(constraint->getSecondType())) + return disjunction; + } + } + + // If we had any binding disjunctions, return the first of + // those. These ensure that we attempt to bind types earlier than + // trying the elements of other disjunctions, which can often mean + // we fail faster. + return firstBindDisjunction; +} + +/// Prioritize `build{Block, Expression, ...}` and any chained +/// members that are connected to individual builder elements +/// i.e. `ForEach(...) { ... }.padding(...)`, once `ForEach` +/// is resolved, `padding` should be prioritized because its +/// requirements can help prune the solution space before the +/// body is checked. +static Constraint * +selectDisjunctionInResultBuilderContext(ConstraintSystem &cs, + ArrayRef disjunctions) { + auto context = AnyFunctionRef::fromDeclContext(cs.DC); + if (!context) + return nullptr; + + if (!cs.getAppliedResultBuilderTransform(context.value())) + return nullptr; + + std::pair best{nullptr, 0}; + for (auto *disjunction : disjunctions) { + auto *member = + getAsExpr(disjunction->getLocator()->getAnchor()); + if (!member) + continue; + + // Attempt `build{Block, Expression, ...} first because they + // provide contextual information for the inner calls. + if (isResultBuilderMethodReference(cs.getASTContext(), member)) + return disjunction; + + Expr *curr = member; + bool disqualified = false; + // Walk up the parent expression chain and check whether this + // disjunction represents one of the members in a chain that + // leads up to `buildExpression` (if defined by the builder) + // or to a pattern binding for `$__builderN` (the walk won't + // find any argument position locations in that case). + while (auto parent = cs.getParentExpr(curr)) { + if (!(isExpr(parent) || isExpr(parent))) { + disqualified = true; + break; + } + + if (auto *call = getAsExpr(parent)) { + // The current parent appears in an argument position. + if (call->getFn() != curr) { + // Allow expressions that appear in a argument position to + // `build{Expression, Block, ...} methods. + if (auto *UDE = getAsExpr(call->getFn())) { + disqualified = + !isResultBuilderMethodReference(cs.getASTContext(), UDE); + } else { + disqualified = true; + } + } + } + + if (disqualified) + break; + + curr = parent; + } + + if (disqualified) + continue; + + if (auto depth = cs.getExprDepth(member)) { + if (!best.first || best.second > depth) + best = std::make_pair(disjunction, depth.value()); + } + } + + return best.first; +} + +std::optional>> +ConstraintSystem::selectDisjunction() { + SmallVector disjunctions; + + collectDisjunctions(disjunctions); + if (disjunctions.empty()) + return std::nullopt; + + if (auto *disjunction = selectBestBindingDisjunction(*this, disjunctions)) + return std::make_pair(disjunction, llvm::TinyPtrVector()); + + llvm::DenseMap favorings; + determineBestChoicesInContext(*this, disjunctions, favorings); + + if (auto *disjunction = + selectDisjunctionInResultBuilderContext(*this, disjunctions)) { + return std::make_pair(disjunction, favorings[disjunction].FavoredChoices); + } + + // Pick the disjunction with the smallest number of favored, then active + // choices. + auto bestDisjunction = std::min_element( + disjunctions.begin(), disjunctions.end(), + [&](Constraint *first, Constraint *second) -> bool { + unsigned firstActive = first->countActiveNestedConstraints(); + unsigned secondActive = second->countActiveNestedConstraints(); + + auto &[firstScore, firstFavoredChoices] = favorings[first]; + auto &[secondScore, secondFavoredChoices] = favorings[second]; + + // Rank based on scores only if both disjunctions are supported. + if (firstScore && secondScore) { + // If both disjunctions have the same score they should be ranked + // based on number of favored/active choices. + if (*firstScore != *secondScore) + return *firstScore > *secondScore; + } + + unsigned numFirstFavored = firstFavoredChoices.size(); + unsigned numSecondFavored = secondFavoredChoices.size(); + + if (numFirstFavored == numSecondFavored) { + if (firstActive != secondActive) + return firstActive < secondActive; + } + + numFirstFavored = numFirstFavored ? numFirstFavored : firstActive; + numSecondFavored = numSecondFavored ? numSecondFavored : secondActive; + + return numFirstFavored < numSecondFavored; + }); + + if (bestDisjunction != disjunctions.end()) + return std::make_pair(*bestDisjunction, + favorings[*bestDisjunction].FavoredChoices); + + return std::nullopt; +} diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index fa526dc79b3f8..1016216eab701 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -9760,7 +9760,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()) { @@ -9768,17 +9767,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); - } - } - } } } @@ -9887,30 +9875,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'. @@ -14726,9 +14690,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 b64174d9307c8..ae85683d84125 100644 --- a/lib/Sema/CSSolver.cpp +++ b/lib/Sema/CSSolver.cpp @@ -799,556 +799,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; - - cs.addConstraint(constraintKind, cs.getType(E), CT, - cs.getConstraintLocator(E), /*isFavored=*/true); - } - - // Try to solve the system and record all available solutions. - llvm::SmallVector solutions; - { - SolverState state(cs, FreeTypeVariableBinding::Allow); - - // Use solve which doesn't try to filter solution list. - // Because we want the whole set of possible domain choices. - cs.solveImpl(solutions); - } - - if (cs.isDebugMode()) { - auto &log = llvm::errs(); - auto indent = cs.solverState ? cs.solverState->getCurrentIndent() : 0; - if (solutions.empty()) { - log << "\n"; - log.indent(indent) << "--- No Solutions ---\n"; - } else { - log << "\n"; - log.indent(indent) << "--- Solutions ---\n"; - for (unsigned i = 0, n = solutions.size(); i != n; ++i) { - auto &solution = solutions[i]; - log << "\n"; - log.indent(indent) << "--- Solution #" << i << " ---\n"; - solution.dump(log, indent); - } - } - } - - // Record found solutions as suggestions. - this->applySolutions(solutions, shrunkExprs); - - // Let's double-check if we have any implicit expressions - // with type variables and nullify their types. - cleanupImplicitExprs(E); - - // No solutions for the sub-expression means that either main expression - // needs salvaging or it's inconsistent (read: doesn't have solutions). - return solutions.empty(); -} - -void ConstraintSystem::Candidate::applySolutions( - llvm::SmallVectorImpl &solutions, - llvm::SmallSetVector &shrunkExprs) const { - // A collection of OSRs with their newly reduced domains, - // it's domains are sets because multiple solutions can have the same - // choice for one of the type variables, and we want no duplication. - llvm::SmallDenseMap> - domains; - for (auto &solution : solutions) { - auto &score = solution.getFixedScore(); - - // Avoid any solutions with implicit value conversions - // because they might get reverted later when more context - // becomes available. - if (score.Data[SK_ImplicitValueConversion] > 0) - continue; - - for (auto choice : solution.overloadChoices) { - // Some of the choices might not have locators. - if (!choice.getFirst()) - continue; - - auto anchor = choice.getFirst()->getAnchor(); - auto *OSR = getAsExpr(anchor); - // Anchor is not available or expression is not an overload set. - if (!OSR) - continue; - - auto overload = choice.getSecond().choice; - auto type = overload.getDecl()->getInterfaceType(); - - // One of the solutions has polymorphic type associated with one of its - // type variables. Such functions can only be properly resolved - // via complete expression, so we'll have to forget solutions - // we have already recorded. They might not include all viable overload - // choices. - if (type->is()) { - return; - } - - domains[OSR].insert(overload.getDecl()); - } - } - - // Reduce the domains. - for (auto &domain : domains) { - auto OSR = domain.getFirst(); - auto &choices = domain.getSecond(); - - // If the domain wasn't reduced, skip it. - if (OSR->getDecls().size() == choices.size()) continue; - - // Update the expression with the reduced domain. - MutableArrayRef decls( - Allocator.Allocate(choices.size()), - choices.size()); - - std::uninitialized_copy(choices.begin(), choices.end(), decls.begin()); - OSR->setDecls(decls); - - // Record successfully shrunk expression. - shrunkExprs.insert(OSR); - } -} - -void ConstraintSystem::shrink(Expr *expr) { - if (getASTContext().TypeCheckerOpts.SolverDisableShrink) - return; - - using DomainMap = llvm::SmallDenseMap>; - - // A collection of original domains of all of the expressions, - // so they can be restored in case of failure. - DomainMap domains; - - struct ExprCollector : public ASTWalker { - Expr *PrimaryExpr; - - // The primary constraint system. - ConstraintSystem &CS; - - // All of the sub-expressions which are suitable to be solved - // separately from the main system e.g. binary expressions, collections, - // function calls, coercions etc. - llvm::SmallVector Candidates; - - // Counts the number of overload sets present in the tree so far. - // Note that the traversal is depth-first. - llvm::SmallVector, 4> ApplyExprs; - - // A collection of original domains of all of the expressions, - // so they can be restored in case of failure. - DomainMap &Domains; - - ExprCollector(Expr *expr, ConstraintSystem &cs, DomainMap &domains) - : PrimaryExpr(expr), CS(cs), Domains(domains) {} - - MacroWalking getMacroWalkingBehavior() const override { - return MacroWalking::Arguments; - } - - PreWalkResult walkToExprPre(Expr *expr) override { - // A dictionary expression is just a set of tuples; try to solve ones - // that have overload sets. - if (auto collectionExpr = dyn_cast(expr)) { - visitCollectionExpr(collectionExpr, - CS.getContextualType(expr, /*forConstraint=*/false), - CS.getContextualTypePurpose(expr)); - // Don't try to walk into the dictionary. - return Action::SkipNode(expr); - } - - // Let's not attempt to type-check closures or expressions - // which constrain closures, because they require special handling - // when dealing with context and parameters declarations. - if (isa(expr)) { - return Action::SkipNode(expr); - } - - // Similar to 'ClosureExpr', 'TapExpr' has a 'VarDecl' the type of which - // is determined by type checking the parent interpolated string literal. - if (isa(expr)) { - return Action::SkipNode(expr); - } - - // Same as TapExpr and ClosureExpr, we'll handle SingleValueStmtExprs - // separately. - if (isa(expr)) - return Action::SkipNode(expr); - - if (auto coerceExpr = dyn_cast(expr)) { - if (coerceExpr->isLiteralInit()) - ApplyExprs.push_back({coerceExpr, 1}); - visitCoerceExpr(coerceExpr); - return Action::SkipNode(expr); - } - - if (auto OSR = dyn_cast(expr)) { - Domains[OSR] = OSR->getDecls(); - } - - if (auto applyExpr = dyn_cast(expr)) { - auto func = applyExpr->getFn(); - // Let's record this function application for post-processing - // as well as if it contains overload set, see walkToExprPost. - ApplyExprs.push_back( - {applyExpr, isa(func) || isa(func)}); - } - - return Action::Continue(expr); - } - - /// Determine whether this is an arithmetic expression comprised entirely - /// of literals. - static bool isArithmeticExprOfLiterals(Expr *expr) { - expr = expr->getSemanticsProvidingExpr(); - - if (auto prefix = dyn_cast(expr)) - return isArithmeticExprOfLiterals(prefix->getOperand()); - - if (auto postfix = dyn_cast(expr)) - return isArithmeticExprOfLiterals(postfix->getOperand()); - - if (auto binary = dyn_cast(expr)) - return isArithmeticExprOfLiterals(binary->getLHS()) && - isArithmeticExprOfLiterals(binary->getRHS()); - - return isa(expr) || isa(expr); - } - - PostWalkResult walkToExprPost(Expr *expr) override { - auto isSrcOfPrimaryAssignment = [&](Expr *expr) -> bool { - if (auto *AE = dyn_cast(PrimaryExpr)) - return expr == AE->getSrc(); - return false; - }; - - if (expr == PrimaryExpr || isSrcOfPrimaryAssignment(expr)) { - // If this is primary expression and there are no candidates - // to be solved, let's not record it, because it's going to be - // solved regardless. - if (Candidates.empty()) - return Action::Continue(expr); - - auto contextualType = CS.getContextualType(expr, - /*forConstraint=*/false); - // If there is a contextual type set for this expression. - if (!contextualType.isNull()) { - Candidates.push_back(Candidate(CS, PrimaryExpr, contextualType, - CS.getContextualTypePurpose(expr))); - return Action::Continue(expr); - } - - // Or it's a function application or assignment with other candidates - // present. Assignment should be easy to solve because we'd get a - // contextual type from the destination expression, otherwise shrink - // might produce incorrect results without considering aforementioned - // destination type. - if (isa(expr) || isa(expr)) { - Candidates.push_back(Candidate(CS, PrimaryExpr)); - return Action::Continue(expr); - } - } - - if (!isa(expr)) - return Action::Continue(expr); - - unsigned numOverloadSets = 0; - // Let's count how many overload sets do we have. - while (!ApplyExprs.empty()) { - auto &application = ApplyExprs.back(); - auto applyExpr = application.first; - - // Add overload sets tracked by current expression. - numOverloadSets += application.second; - ApplyExprs.pop_back(); - - // We've found the current expression, so record the number of - // overloads. - if (expr == applyExpr) { - ApplyExprs.push_back({applyExpr, numOverloadSets}); - break; - } - } - - // If there are fewer than two overloads in the chain - // there is no point of solving this expression, - // because we won't be able to reduce its domain. - if (numOverloadSets > 1 && !isArithmeticExprOfLiterals(expr)) - Candidates.push_back(Candidate(CS, expr)); - - return Action::Continue(expr); - } - - private: - /// Extract type of the element from given collection type. - /// - /// \param collection The type of the collection container. - /// - /// \returns Null type, ErrorType or UnresolvedType on failure, - /// properly constructed type otherwise. - Type extractElementType(Type collection) { - auto &ctx = CS.getASTContext(); - if (!collection || collection->hasError()) - return collection; - - auto base = collection.getPointer(); - auto isInvalidType = [](Type type) -> bool { - return type.isNull() || type->hasUnresolvedType() || - type->hasError(); - }; - - // Array type. - if (auto array = dyn_cast(base)) { - auto elementType = array->getBaseType(); - // If base type is invalid let's return error type. - return elementType; - } - - // Map or Set or any other associated collection type. - if (auto boundGeneric = dyn_cast(base)) { - if (boundGeneric->hasUnresolvedType()) - return boundGeneric; - - llvm::SmallVector params; - for (auto &type : boundGeneric->getGenericArgs()) { - // One of the generic arguments in invalid or unresolved. - if (isInvalidType(type)) - return type; - - params.push_back(type); - } - - // If there is just one parameter, let's return it directly. - if (params.size() == 1) - return params[0].getType(); - - return TupleType::get(params, ctx); - } - - return Type(); - } - - bool isSuitableCollection(TypeRepr *collectionTypeRepr) { - // Only generic identifier, array or dictionary. - switch (collectionTypeRepr->getKind()) { - case TypeReprKind::UnqualifiedIdent: - return cast(collectionTypeRepr) - ->hasGenericArgList(); - - case TypeReprKind::Array: - case TypeReprKind::Dictionary: - return true; - - default: - break; - } - - return false; - } - - void visitCoerceExpr(CoerceExpr *coerceExpr) { - auto subExpr = coerceExpr->getSubExpr(); - // Coerce expression is valid only if it has sub-expression. - if (!subExpr) return; - - unsigned numOverloadSets = 0; - subExpr->forEachChildExpr([&](Expr *childExpr) -> Expr * { - if (isa(childExpr)) { - ++numOverloadSets; - return childExpr; - } - - if (auto nestedCoerceExpr = dyn_cast(childExpr)) { - visitCoerceExpr(nestedCoerceExpr); - // Don't walk inside of nested coercion expression directly, - // that is be done by recursive call to visitCoerceExpr. - return nullptr; - } - - // If sub-expression we are trying to coerce to type is a collection, - // let's allow collector discover it with assigned contextual type - // of coercion, which allows collections to be solved in parts. - if (auto collectionExpr = dyn_cast(childExpr)) { - auto *const typeRepr = coerceExpr->getCastTypeRepr(); - - if (typeRepr && isSuitableCollection(typeRepr)) { - const auto coercionType = TypeResolution::resolveContextualType( - typeRepr, CS.DC, std::nullopt, - // FIXME: Should we really be unconditionally complaining - // about unbound generics and placeholders here? For - // example: - // let foo: [Array] = [[0], [1], [2]] as [Array] - // let foo: [Array] = [[0], [1], [2]] as [Array<_>] - /*unboundTyOpener*/ nullptr, /*placeholderHandler*/ nullptr, - /*packElementOpener*/ nullptr); - - // Looks like coercion type is invalid, let's skip this sub-tree. - if (coercionType->hasError()) - return nullptr; - - // Visit collection expression inline. - visitCollectionExpr(collectionExpr, coercionType, - CTP_CoerceOperand); - } - } - - return childExpr; - }); - - // It's going to be inefficient to try and solve - // coercion in parts, so let's just make it a candidate directly, - // if it contains at least a single overload set. - - if (numOverloadSets > 0) - Candidates.push_back(Candidate(CS, coerceExpr)); - } - - void visitCollectionExpr(CollectionExpr *collectionExpr, - Type contextualType = Type(), - ContextualTypePurpose CTP = CTP_Unused) { - // If there is a contextual type set for this collection, - // let's propagate it to the candidate. - if (!contextualType.isNull()) { - auto elementType = extractElementType(contextualType); - // If we couldn't deduce element type for the collection, let's - // not attempt to solve it. - if (!elementType || - elementType->hasError() || - elementType->hasUnresolvedType()) - return; - - contextualType = elementType; - } - - for (auto element : collectionExpr->getElements()) { - unsigned numOverloads = 0; - element->walk(OverloadSetCounter(numOverloads)); - - // There are no overload sets in the element; skip it. - if (numOverloads == 0) - continue; - - // Record each of the collection elements, which passed - // number of overload sets rule, as a candidate for solving - // with contextual type of the collection. - Candidates.push_back(Candidate(CS, element, contextualType, CTP)); - } - } - }; - - ExprCollector collector(expr, *this, domains); - - // Collect all of the binary/unary and call sub-expressions - // so we can start solving them separately. - expr->walk(collector); - - llvm::SmallSetVector shrunkExprs; - for (auto &candidate : collector.Candidates) { - // If there are no results, let's forget everything we know about the - // system so far. This actually is ok, because some of the expressions - // might require manual salvaging. - if (candidate.solve(shrunkExprs)) { - // Let's restore all of the original OSR domains for this sub-expression, - // this means that we can still make forward progress with solving of the - // top sub-expressions. - candidate.getExpr()->forEachChildExpr([&](Expr *childExpr) -> Expr * { - if (auto OSR = dyn_cast(childExpr)) { - auto domain = domains.find(OSR); - if (domain == domains.end()) - return childExpr; - - OSR->setDecls(domain->getSecond()); - shrunkExprs.remove(OSR); - } - - return childExpr; - }); - } - } - - // Once "shrinking" is done let's re-allocate final version of - // the candidate list to the permanent arena, so it could - // survive even after primary constraint system is destroyed. - for (auto &OSR : shrunkExprs) { - auto choices = OSR->getDecls(); - auto decls = - getASTContext().AllocateUninitialized(choices.size()); - - std::uninitialized_copy(choices.begin(), choices.end(), decls.begin()); - OSR->setDecls(decls); - } -} - static bool debugConstraintSolverForTarget(ASTContext &C, SyntacticElementTarget target) { if (C.TypeCheckerOpts.DebugConstraintSolver) @@ -1715,8 +1165,6 @@ bool ConstraintSystem::solveForCodeCompletion( // Set up the expression type checker timer. startExpressionTimer(expr); - - shrink(expr); } if (isDebugMode()) { @@ -1849,62 +1297,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().gatherConstraints( - typeVar, ConstraintGraph::GatheringKind::EquivalenceClass, - [](Constraint *constraint) { - return constraint->getKind() == ConstraintKind::Conversion; - }); - - for (auto *constraint : constraints) { - if (typeVar == getAsTypeVar(constraint->getSecondType())) - return disjunction; - } - } - - // If we had any binding disjunctions, return the first of - // those. These ensure that we attempt to bind types earlier than - // trying the elements of other disjunctions, which can often mean - // we fail faster. - return firstBindDisjunction; -} std::optional> ConstraintSystem::findConstraintThroughOptionals( @@ -2010,6 +1402,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) @@ -2380,61 +1793,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 d186a4960040b..c30640a993b7e 100644 --- a/lib/Sema/CSStep.cpp +++ b/lib/Sema/CSStep.cpp @@ -360,7 +360,7 @@ StepResult ComponentStep::take(bool prevFailed) { } }); - auto *disjunction = CS.selectDisjunction(); + auto disjunction = CS.selectDisjunction(); auto *conjunction = CS.selectConjunction(); if (CS.isDebugMode()) { @@ -403,7 +403,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)) @@ -426,9 +427,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); diff --git a/lib/Sema/CSStep.h b/lib/Sema/CSStep.h index 3b8c39e4a015d..a3e75b93f0136 100644 --- a/lib/Sema/CSStep.h +++ b/lib/Sema/CSStep.h @@ -671,26 +671,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; @@ -743,46 +745,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()) { - 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 1b4395b25cfc1..8261e009589ac 100644 --- a/lib/Sema/Constraint.cpp +++ b/lib/Sema/Constraint.cpp @@ -711,17 +711,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 4ddc6c32455ca..0f45f3b37bfaa 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -204,6 +204,14 @@ bool TypeVariableType::Implementation::isCollectionLiteralType() const { locator->directlyAt()); } +bool TypeVariableType::Implementation::isNumberLiteralType() const { + return locator && locator->directlyAt(); +} + +bool TypeVariableType::Implementation::isFunctionResult() const { + return locator && locator->isLastElement(); +} + void *operator new(size_t bytes, ConstraintSystem& cs, size_t alignment) { return cs.getAllocator().Allocate(bytes, alignment); @@ -450,10 +458,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 6a4fda5e14268..e9a15d8169887 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -1400,6 +1400,29 @@ class ApplyClassifier { // then look at all of the closure arguments. LLVM_FALLTHROUGH; } else { + // Mitigation for a historic incorrect type-checker behavior + // caused by one of the performance hacks that used to favor + // sync constructor overload over async one in async context + // if initializers take a single unlabeled argument. + // + // struct S { + // init(_: Int) {} + // init(_: Int) async {} + // } + // + // func test(v: Int) async { S(v) } + // + // The type-checker should use `init(_: Int) async` in `test` context + // but used to select the sync overload. The hack is now gone but we + // need to downgrade an error to a warning to give the developers time + // to fix their code. + if (kind == EffectKind::Async && + fnRef.getKind() == AbstractFunction::Function) { + if (auto *ctor = dyn_cast(E->getFn())) { + if (ctor->getFn()->isImplicit() && args->isUnlabeledUnary()) + result.setDowngradeToWarning(true); + } + } break; } diff --git a/lib/Sema/TypeOfReference.cpp b/lib/Sema/TypeOfReference.cpp index a33ca48501a09..fac56c1b8f9df 100644 --- a/lib/Sema/TypeOfReference.cpp +++ b/lib/Sema/TypeOfReference.cpp @@ -1833,11 +1833,15 @@ 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()) + return Type(); + } // Cope with 'Self' returns. if (!decl->getDeclContext()->getSelfProtocolDecl()) { diff --git a/test/Constraints/argument_matching.swift b/test/Constraints/argument_matching.swift index 2b78c083000c4..57f173eb0d19d 100644 --- a/test/Constraints/argument_matching.swift +++ b/test/Constraints/argument_matching.swift @@ -1488,15 +1488,19 @@ func trailingclosure4(f: () -> Int) {} trailingclosure4 { 5 } func trailingClosure5(_ file: String = #file, line: UInt = #line, expression: () -> T?) { } +// expected-note@-1 {{in call to function 'trailingClosure5(_:line:expression:)'}} func trailingClosure6(value: Int, expression: () -> T?) { } +// expected-note@-1 {{in call to function 'trailingClosure6(value:expression:)'}} trailingClosure5(file: "hello", line: 17) { // expected-error{{extraneous argument label 'file:' in call}}{{18-24=}} + // expected-error@-1 {{generic parameter 'T' could not be inferred}} return Optional.Some(5) // expected-error@-1 {{enum type 'Optional' has no case 'Some'; did you mean 'some'?}} {{19-23=some}} // expected-error@-2 {{generic parameter 'Wrapped' could not be inferred}} // expected-note@-3 {{explicitly specify the generic arguments to fix this issue}} } trailingClosure6(5) { // expected-error{{missing argument label 'value:' in call}}{{18-18=value: }} + // expected-error@-1 {{generic parameter 'T' could not be inferred}} return Optional.Some(5) // expected-error@-1 {{enum type 'Optional' has no case 'Some'; did you mean 'some'?}} {{19-23=some}} // expected-error@-2 {{generic parameter 'Wrapped' could not be inferred}} diff --git a/test/Constraints/async.swift b/test/Constraints/async.swift index f914d49b56372..9985f169c0a22 100644 --- a/test/Constraints/async.swift +++ b/test/Constraints/async.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift +// RUN: %target-typecheck-verify-swift // REQUIRES: concurrency @@ -197,3 +197,24 @@ func test_sync_in_closure_context() { test(42) // Ok (select sync overloads and discards the result) } } + +@available(SwiftStdlib 5.5, *) +func test_async_calls_in_async_context(v: Int) async { + final class Test : Sendable { + init(_: Int) {} + init(_: Int) async {} + + func test(_: Int) {} + func test(_: Int) async {} + + static func test(_: Int) {} + static func test(_: Int) async {} + } + + // Only implicit `.init` should be accepted with a warning due type-checker previously picking an incorrect overload. + _ = Test(v) // expected-warning {{expression is 'async' but is not marked with 'await'; this is an error in the Swift 6 language mode}} expected-note {{call is 'async'}} + _ = Test.init(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note {{call is 'async'}} + + Test.test(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note {{call is 'async'}} + Test(v).test(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note 2 {{call is 'async'}} +} 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 32126db0864d0..83c6955851b58 100644 --- a/test/Constraints/implicit_double_cgfloat_conversion.swift +++ b/test/Constraints/implicit_double_cgfloat_conversion.swift @@ -342,3 +342,27 @@ func test_init_validation() { } } } + +do { + struct G { + init(_: T) {} + } + + func round(_: Double) -> Double {} + func round(_: T) -> T {} + + func test_cgfloat_over_double(withColors colors: Int, size: CGSize) -> G { + let g = G(1.0 / CGFloat(colors)) + return g // Ok + } + + func test_no_ambiguity(width: Int, height: Int) -> CGFloat { + let v = round(CGFloat(width / height) * 10) / 10.0 + return v // Ok + } +} + +func test_cgfloat_operator_is_attempted_with_literal_arguments(v: CGFloat?) { + let ratio = v ?? (2.0 / 16.0) + let _: CGFloat = ratio // Ok +} diff --git a/test/Constraints/old_hack_related_ambiguities.swift b/test/Constraints/old_hack_related_ambiguities.swift new file mode 100644 index 0000000000000..da8da45cc76dc --- /dev/null +++ b/test/Constraints/old_hack_related_ambiguities.swift @@ -0,0 +1,259 @@ +// RUN: %target-typecheck-verify-swift + +func entity(_: Int) -> Int { + 0 +} + +struct Test { + func test(_ v: Int) -> Int { v } + func test(_ v: Int?) -> Int? { v } +} + +func test_ternary_literal(v: Test) -> Int? { + true ? v.test(0) : nil // Ok +} + +func test_ternary(v: Test) -> Int? { + true ? v.test(entity(0)) : nil // Ok +} + +do { + struct TestFloat { + func test(_ v: Float) -> Float { v } // expected-note {{found this candidate}} + func test(_ v: Float?) -> Float? { v } // expected-note {{found this candidate}} + } + + func test_ternary_non_default_literal(v: TestFloat) -> Float? { + true ? v.test(1.0) : nil // expected-error {{ambiguous use of 'test'}} + } +} + +do { + struct Test { + init(a: Int, b: Int = 0) throws {} + init?(a: Int?) {} + } + + func test(v: Int) -> Test? { + return Test(a: v) // Ok + } +} + +// error: initializer for conditional binding must have Optional type, not 'S' +do { + struct S { + let n: Int + + func test(v: String) -> Int { } + func test(v: String, flag: Bool = false) -> Int? { } + + + func verify(v: String) -> Int? { + guard let _ = test(v: v) else { // Ok + return nil + } + return 0 + } + } + + func f(_: String, _ p: Bool = false) -> S? { + nil + } + + func f(_ x: String) -> S { + fatalError() + } + + func g(_ x: String) -> Int? { + guard let y = f(x) else { + return nil + } + return y.n + } +} + +// ambiguities related to ~= +protocol _Error: Error {} + +extension _Error { + public static func ~=(lhs: Self, rhs: Self) -> Bool { + false + } + + public static func ~=(lhs: Error, rhs: Self) -> Bool { + false + } + + public static func ~=(lhs: Self, rhs: Error) -> Bool { + false + } +} + +enum CustomError { + case A +} + +extension CustomError: _Error {} + +func f(e: CustomError) { + if e ~= CustomError.A {} +} + +// Generic overload should be preferred over concrete one because the latter is non-default literal +struct Pattern {} + +func ~= (pattern: Pattern, value: String) -> Bool { + return false +} + +extension Pattern: ExpressibleByStringLiteral { + init(stringLiteral value: String) {} +} + +func test_default_tilda(v: String) { + _ = "hi" ~= v // Ok +} + +struct UUID {} + +struct LogKey { + init(base: some CustomStringConvertible, foo: Int = 0) { + } + + init(base: UUID, foo: Int = 0) { + } +} + +@available(swift 99) +extension LogKey { + init(base: String?) { + } + + init(base: UUID?) { + } +} + +func test_that_unavailable_init_is_not_used(x: String?) { + _ = LogKey(base: x ?? "??") +} + +// error: value of optional type 'UID?' must be unwrapped to a value of type 'UID' +struct S: Comparable { + static func <(lhs: Self, rhs: Self) -> Bool { + false + } +} + +func max(_ a: S?, _ b: S?) -> S? { + nil +} + +func test_stdlib_max_selection(s: S) -> S { + let new = max(s, s) + return new // Ok +} + +// error: initializer for conditional binding must have Optional type, not 'UnsafeMutablePointer' +do { + struct TestPointerConversions { + var p: UnsafeMutableRawPointer { get { fatalError() } } + + func f(_ p: UnsafeMutableRawPointer) { + guard let x = UnsafeMutablePointer(OpaquePointer(self.p)) else { + return + } + _ = x + + guard let x = UnsafeMutablePointer(OpaquePointer(p)) else { + return + } + _ = x + } + } +} + +// error: initializer 'init(_:)' requires that 'T' conform to 'BinaryInteger' +do { + struct Config { + subscript(_ key: String) -> T? { nil } + subscript(_ key: String) -> Any? { nil } + } + + struct S { + init(maxQueueDepth: UInt) {} + } + + func f(config: Config) { + let maxQueueDepth = config["hi"] ?? 256 + _ = S(maxQueueDepth: UInt(maxQueueDepth)) + } +} + +// `tryOptimizeGenericDisjunction` is too aggressive sometimes, make sure that `` +// overload is _not_ selected in this case. +do { + func test(_ expression1: @autoclosure () throws -> T, accuracy: T) -> T {} + func test(_ expression1: @autoclosure () throws -> T, accuracy: T) -> T {} + + let result = test(10, accuracy: 1) + let _: Int = result +} + +// swift-distributed-tracing snippet that relies on old hack behavior. +protocol TracerInstant { +} + +extension Int: TracerInstant {} + +do { + enum SpanKind { + case `internal` + } + + func withSpan( + _ operationName: String, + at instant: @autoclosure () -> Instant, + context: @autoclosure () -> Int = 0, + ofKind kind: SpanKind = .internal + ) {} + + func withSpan( + _ operationName: String, + context: @autoclosure () -> Int = 0, + ofKind kind: SpanKind = .internal, + at instant: @autoclosure () -> some TracerInstant = 42 + ) {} + + withSpan("", at: 0) // Ok +} + +protocol ForAssert { + var isEmpty: Bool { get } +} + +extension ForAssert { + var isEmpty: Bool { false } +} + +do { + func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) {} + func assert(_ condition: Bool, _ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) {} + func assert(_ condition: Bool, file: StaticString = #fileID, line: UInt = #line) {} + + struct S : ForAssert { + var isEmpty: Bool { false } + } + + func test(s: S) { + assert(s.isEmpty, "") // Ok + } +} + +extension Double { + public static func * (left: Float, right: Double) -> Double { 0 } +} + +func test_non_default_literal_use(arg: Float) { + let v = arg * 2.0 // shouldn't use `(Float, Double) -> Double` overload + let _: Float = v // Ok +} diff --git a/test/Constraints/operator.swift b/test/Constraints/operator.swift index 4770481c95e30..d12bcbbc20a04 100644 --- a/test/Constraints/operator.swift +++ b/test/Constraints/operator.swift @@ -329,3 +329,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/IDE/complete_operators.swift b/test/IDE/complete_operators.swift index a349fd4ecf8da..483d3cc6225d8 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/expr/expressions.swift b/test/expr/expressions.swift index d7ddc5fc0c765..4a4d8c3232ce8 100644 --- a/test/expr/expressions.swift +++ b/test/expr/expressions.swift @@ -758,10 +758,10 @@ func invalidDictionaryLiteral() { //===----------------------------------------------------------------------===// // nil/metatype comparisons //===----------------------------------------------------------------------===// -_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns false}} -_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns false}} -_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}} -_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}} +_ = Int.self == 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 Any.Type' to 'Optional.none' always returns false}} _ = .none == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}} diff --git a/validation-test/Sema/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/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/complex_swiftui_padding_conditions.swift b/validation-test/Sema/type_checker_perf/fast/complex_swiftui_padding_conditions.swift new file mode 100644 index 0000000000000..df038a16ff4cc --- /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 -swift-version 5 +// REQUIRES: OS=macosx + +import SwiftUI + +func test(a: [(offset: Int, element: Double)], + c: Color, + x: CGFloat, + n: Int +) -> some View { + ForEach(a, id: \.offset) { i, r in + RoundedRectangle(cornerRadius: r, style: .continuous) + .stroke(c, lineWidth: 1) + .padding(.horizontal, x / Double(n) * Double(n - 1 - i) / 2) + .padding(.vertical, x / Double(n) * Double(n - 1 - i) / 2) + } +} diff --git a/validation-test/Sema/type_checker_perf/fast/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/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/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/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/rdar22079400.swift b/validation-test/Sema/type_checker_perf/fast/rdar22079400.swift similarity index 74% rename from validation-test/Sema/type_checker_perf/slow/rdar22079400.swift rename to validation-test/Sema/type_checker_perf/fast/rdar22079400.swift index ea8334f5b28fa..3f6a797c4b5f3 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar22079400.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar22079400.swift @@ -1,7 +1,7 @@ // RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 // REQUIRES: tools-release,no_asan -let _ = (0...1).lazy.flatMap { // expected-error {{reasonable time}} +let _ = (0...1).lazy.flatMap { a in (1...2).lazy.map { b in (a, b) } }.filter { 1 < $0 && $0 < $1 && $0 + $1 < 3 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/slow/rdar23682605.swift b/validation-test/Sema/type_checker_perf/fast/rdar23682605.swift similarity index 91% rename from validation-test/Sema/type_checker_perf/slow/rdar23682605.swift rename to validation-test/Sema/type_checker_perf/fast/rdar23682605.swift index b7bc757fe1989..4be53b4d2ef83 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar23682605.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar23682605.swift @@ -14,7 +14,6 @@ func memoize( body: @escaping ((T)->U, T)->U ) -> (T)->U { } let fibonacci = memoize { - // expected-error@-1 {{reasonable time}} fibonacci, n in n < 2 ? Double(n) : fibonacci(n - 1) + fibonacci(n - 2) } diff --git a/validation-test/Sema/type_checker_perf/slow/rdar31742586.swift b/validation-test/Sema/type_checker_perf/fast/rdar31742586.swift similarity index 67% rename from validation-test/Sema/type_checker_perf/slow/rdar31742586.swift rename to validation-test/Sema/type_checker_perf/fast/rdar31742586.swift index 0cc33ce8253cf..2c694c570a137 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar31742586.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar31742586.swift @@ -3,5 +3,4 @@ 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/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/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/slow/simd_scalar_multiple.swift.gyb b/validation-test/Sema/type_checker_perf/fast/simd_scalar_multiple.swift.gyb similarity index 61% 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 30d886028d7b5..6d189530e690a 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: no_asan 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/stdlib/FixedPointDiagnostics.swift.gyb b/validation-test/stdlib/FixedPointDiagnostics.swift.gyb index e091805af22b7..2b2df2812c0d2 100644 --- a/validation-test/stdlib/FixedPointDiagnostics.swift.gyb +++ b/validation-test/stdlib/FixedPointDiagnostics.swift.gyb @@ -53,8 +53,8 @@ func testMixedSignArithmetic() { Stride(1) - ${T}(0) // expected-error {{}} expected-note {{}} var y: Stride = 0 - y += ${T}(1) // expected-error {{cannot convert value of type '${T}' to expected argument type 'Int'}} {{10-10=Int(}} - y -= ${T}(1) // expected-error {{cannot convert value of type '${T}' to expected argument type 'Int'}} {{10-10=Int(}} + y += ${T}(1) // expected-error {{cannot convert value of type '${T}' to expected argument type 'Stride' (aka 'Int')}} {{10-10=Stride(}} + y -= ${T}(1) // expected-error {{cannot convert value of type '${T}' to expected argument type 'Stride' (aka 'Int')}} {{10-10=Stride(}} } % end }