diff --git a/include/swift/AST/KnownStdlibTypes.def b/include/swift/AST/KnownStdlibTypes.def index 425c846e26854..77b9d5b84e463 100644 --- a/include/swift/AST/KnownStdlibTypes.def +++ b/include/swift/AST/KnownStdlibTypes.def @@ -70,6 +70,7 @@ KNOWN_STDLIB_TYPE_DECL(WritableKeyPath, NominalTypeDecl, 2) KNOWN_STDLIB_TYPE_DECL(ReferenceWritableKeyPath, NominalTypeDecl, 2) KNOWN_STDLIB_TYPE_DECL(Optional, EnumDecl, 1) +KNOWN_STDLIB_TYPE_DECL(_OptionalNilComparisonType, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(OptionSet, NominalTypeDecl, 1) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 0c26d2ba2414e..d0c515ed4c418 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -5370,6 +5370,22 @@ class DefaultIsolationInSourceFileRequest bool isCached() const { return true; } }; +class ModuleHasTypeCheckerPerformanceHacksEnabledRequest + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + bool evaluate(Evaluator &evaluator, const ModuleDecl *module) const; + +public: + bool isCached() const { return true; } +}; + #define SWIFT_TYPEID_ZONE TypeChecker #define SWIFT_TYPEID_HEADER "swift/AST/TypeCheckerTypeIDZone.def" #include "swift/Basic/DefineTypeIDZone.h" diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index c1bed6f15c46d..9028c6e97c2f4 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -637,3 +637,7 @@ SWIFT_REQUEST(TypeChecker, SemanticAvailabilitySpecRequest, SWIFT_REQUEST(TypeChecker, DefaultIsolationInSourceFileRequest, std::optional(const SourceFile *), Cached, NoLocationInfo) + +SWIFT_REQUEST(TypeChecker, ModuleHasTypeCheckerPerformanceHacksEnabledRequest, + bool(const ModuleDecl *), + Cached, NoLocationInfo) \ No newline at end of file diff --git a/include/swift/Basic/BlockListAction.def b/include/swift/Basic/BlockListAction.def index 8d9839f019ce3..ffa570e27272f 100644 --- a/include/swift/Basic/BlockListAction.def +++ b/include/swift/Basic/BlockListAction.def @@ -26,5 +26,6 @@ BLOCKLIST_ACTION(ShouldUseLayoutStringValueWitnesses) BLOCKLIST_ACTION(ShouldDisableOwnershipVerification) BLOCKLIST_ACTION(SkipEmittingFineModuleTrace) BLOCKLIST_ACTION(SkipIndexingModule) +BLOCKLIST_ACTION(ShouldUseTypeCheckerPerfHacks) #undef BLOCKLIST_ACTION diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index d6a9e36ecbf8b..f1896d42383f7 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -986,8 +986,8 @@ namespace swift { /// Enable experimental operator designated types feature. bool EnableOperatorDesignatedTypes = false; - /// Disable constraint system performance hacks. - bool DisableConstraintSolverPerformanceHacks = false; + /// Enable old constraint system performance hacks. + bool EnableConstraintSolverPerformanceHacks = false; /// See \ref FrontendOptions.PrintFullConvention bool PrintFullConvention = false; diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 3a353ddbca74a..1ca56c6cff83e 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -862,8 +862,8 @@ def solver_trail_threshold_EQ : Joined<["-"], "solver-trail-threshold=">, def solver_disable_splitter : Flag<["-"], "solver-disable-splitter">, HelpText<"Disable the component splitter phase of expression type checking">; -def disable_constraint_solver_performance_hacks : Flag<["-"], "disable-constraint-solver-performance-hacks">, - HelpText<"Disable all the hacks in the constraint solver">; +def enable_constraint_solver_performance_hacks : Flag<["-"], "enable-constraint-solver-performance-hacks">, + HelpText<"Enable all the old hacks in the constraint solver">; def enable_operator_designated_types : Flag<["-"], "enable-operator-designated-types">, diff --git a/include/swift/Sema/CSBindings.h b/include/swift/Sema/CSBindings.h index e67ad8602ee8b..b47bb84294f2e 100644 --- a/include/swift/Sema/CSBindings.h +++ b/include/swift/Sema/CSBindings.h @@ -527,6 +527,12 @@ class BindingSet { void forEachLiteralRequirement( llvm::function_ref callback) const; + void forEachAdjacentVariable( + llvm::function_ref callback) const { + for (auto *typeVar : AdjacentVars) + callback(typeVar); + } + /// Return a literal requirement that has the most impact on the binding /// score. LiteralBindingKind getLiteralForScore() const; diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index c2550698d7cea..abb99fe9a1d21 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -491,6 +491,18 @@ class TypeVariableType::Implementation { /// literal (represented by `ArrayExpr` and `DictionaryExpr` in AST). bool isCollectionLiteralType() const; + /// Determine whether this type variable represents a literal such + /// as an integer value, a floating-point value with and without a sign. + bool isNumberLiteralType() const; + + /// Determine whether this type variable represents a result type of a + /// function call. + bool isFunctionResult() const; + + /// Determine whether this type variable represents a type of the ternary + /// operator. + bool isTernary() const; + /// Retrieve the representative of the equivalence class to which this /// type variable belongs. /// @@ -1952,6 +1964,9 @@ enum class ConstraintSystemFlags { /// Disable macro expansions. DisableMacroExpansions = 0x80, + + /// Enable old type-checker performance hacks. + EnablePerformanceHacks = 0x100, }; /// Options that affect the constraint system as a whole. @@ -3591,11 +3606,10 @@ class ConstraintSystem { return Options.contains(ConstraintSystemFlags::ForCodeCompletion); } - /// Check whether type-checker performance hacks has been explicitly - /// disabled by a flag. + /// Check whether old type-checker performance hacks has been explicitly + /// enabled. bool performanceHacksEnabled() const { - return !getASTContext() - .TypeCheckerOpts.DisableConstraintSolverPerformanceHacks; + return Options.contains(ConstraintSystemFlags::EnablePerformanceHacks); } /// Log and record the application of the fix. Return true iff any @@ -5397,8 +5411,12 @@ class ConstraintSystem { /// 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(); + + /// The old method that is only used when performance hacks are enabled. + Constraint *selectDisjunctionWithHacks(); /// Pick a conjunction from the InactiveConstraints list. /// @@ -6098,6 +6116,12 @@ class DisjunctionChoice { return false; } + bool isDisfavored() const { + if (auto *decl = getOverloadChoiceDecl(Choice)) + return decl->getAttrs().hasAttribute(); + return false; + } + bool isBeginningOfPartition() const { return IsBeginningOfPartition; } // FIXME: All three of the accessors below are required to support @@ -6332,7 +6356,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), @@ -6342,6 +6367,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); } @@ -6386,8 +6416,9 @@ class DisjunctionChoiceProducer : public BindingProducer { // Partition the choices in the disjunction into groups that we will // iterate over in an order appropriate to attempt to stop before we // have to visit all of the options. - void partitionDisjunction(SmallVectorImpl &Ordering, - SmallVectorImpl &PartitionBeginning); + void + partitionDisjunction(SmallVectorImpl &Ordering, + SmallVectorImpl &PartitionBeginning); /// Partition the choices in the range \c first to \c last into groups and /// order the groups in the best order to attempt based on the argument diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index afbf32fdbb1a4..b658685952fb9 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -2009,8 +2009,8 @@ static bool ParseTypeCheckerArgs(TypeCheckerOptions &Opts, ArgList &Args, SWIFT_ONONE_SUPPORT); } - Opts.DisableConstraintSolverPerformanceHacks |= - Args.hasArg(OPT_disable_constraint_solver_performance_hacks); + Opts.EnableConstraintSolverPerformanceHacks |= + Args.hasArg(OPT_enable_constraint_solver_performance_hacks); Opts.EnableOperatorDesignatedTypes |= Args.hasArg(OPT_enable_operator_designated_types); diff --git a/lib/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index 84ae48ce0f610..96d8839268e29 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -14,6 +14,7 @@ add_swift_host_library(swiftSema STATIC CSStep.cpp CSTrail.cpp CSFix.cpp + CSOptimizer.cpp CSDiagnostics.cpp CodeSynthesis.cpp CodeSynthesisDistributedActor.cpp diff --git a/lib/Sema/CSBindings.cpp b/lib/Sema/CSBindings.cpp index 807cdec5f30fc..54ad8b4a191d6 100644 --- a/lib/Sema/CSBindings.cpp +++ b/lib/Sema/CSBindings.cpp @@ -31,7 +31,6 @@ using namespace swift; using namespace constraints; using namespace inference; - void ConstraintGraphNode::initBindingSet() { ASSERT(!hasBindingSet()); ASSERT(forRepresentativeVar()); @@ -39,6 +38,12 @@ void ConstraintGraphNode::initBindingSet() { Set.emplace(CG.getConstraintSystem(), TypeVar, Potential); } +/// Check whether there exists a type that could be implicitly converted +/// to a given type i.e. is the given type is Double or Optional<..> this +/// function is going to return true because CGFloat could be converted +/// to a Double and non-optional value could be injected into an optional. +static bool hasConversions(Type); + static std::optional checkTypeOfBinding(TypeVariableType *typeVar, Type type); @@ -1348,7 +1353,31 @@ bool BindingSet::isViable(PotentialBinding &binding, bool isTransitive) { if (!existingNTD || NTD != existingNTD) continue; - // FIXME: What is going on here needs to be thoroughly re-evaluated. + // What is going on in this method needs to be thoroughly re-evaluated! + // + // This logic aims to skip dropping bindings if + // collection type has conversions i.e. in situations like: + // + // [$T1] conv $T2 + // $T2 conv [(Int, String)] + // $T2.Element equal $T5.Element + // + // `$T1` could be bound to `(i: Int, v: String)` after + // `$T2` is bound to `[(Int, String)]` which is is a problem + // because it means that `$T2` was attempted to early + // before the solver had a chance to discover all viable + // bindings. + // + // Let's say existing binding is `[(Int, String)]` and + // relation is "exact", in this case there is no point + // tracking `[$T1]` because upcasts are only allowed for + // subtype and other conversions. + if (existing->Kind != AllowedBindingKind::Exact) { + if (existingType->isKnownStdlibCollectionType() && + hasConversions(existingType)) { + continue; + } + } // If new type has a type variable it shouldn't // be considered viable. diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index ea9e4119faf3e..cc165c6876cbe 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -1051,6 +1051,61 @@ namespace { return tv; } + /// Attempt to infer a result type of a subscript reference where + /// the base type is either a stdlib Array or a Dictionary type. + /// This is a more principled version of the old performance hack + /// that used "favored" types deduced by the constraint optimizer + /// and is important to maintain pre-existing solver behavior. + Type inferCollectionSubscriptResultType(Type baseTy, + ArgumentList *argumentList) { + auto isLValueBase = false; + auto baseObjTy = baseTy; + if (baseObjTy->is()) { + isLValueBase = true; + baseObjTy = baseObjTy->getWithoutSpecifierType(); + } + + auto subscriptResultType = [&isLValueBase](Type valueTy, + bool isOptional) -> Type { + Type outputTy = isOptional ? OptionalType::get(valueTy) : valueTy; + return isLValueBase ? LValueType::get(outputTy) : outputTy; + }; + + if (auto *argument = argumentList->getUnlabeledUnaryExpr()) { + auto argumentTy = CS.getType(argument); + + auto elementTy = baseObjTy->getArrayElementType(); + + if (!elementTy) + elementTy = baseObjTy->getInlineArrayElementType(); + + if (elementTy) { + if (auto arraySliceTy = + dyn_cast(baseObjTy.getPointer())) { + baseObjTy = arraySliceTy->getDesugaredType(); + } + + if (argumentTy->isInt() || isExpr(argument)) + return subscriptResultType(elementTy, /*isOptional*/ false); + } else if (auto dictTy = CS.isDictionaryType(baseObjTy)) { + auto [keyTy, valueTy] = *dictTy; + + if (keyTy->isString() && + (isExpr(argument) || + isExpr(argument))) + return subscriptResultType(valueTy, /*isOptional*/ true); + + if (keyTy->isInt() && isExpr(argument)) + return subscriptResultType(valueTy, /*isOptional*/ true); + + if (keyTy->isEqual(argumentTy)) + return subscriptResultType(valueTy, /*isOptional*/ true); + } + } + + return Type(); + } + /// Add constraints for a subscript operation. Type addSubscriptConstraints( Expr *anchor, Type baseTy, ValueDecl *declOrNull, ArgumentList *argList, @@ -1074,52 +1129,10 @@ namespace { Type outputTy; - // For an integer subscript expression on an array slice type, instead of - // introducing a new type variable we can easily obtain the element type. - if (isa(anchor)) { - - auto isLValueBase = false; - auto baseObjTy = baseTy; - if (baseObjTy->is()) { - isLValueBase = true; - baseObjTy = baseObjTy->getWithoutSpecifierType(); - } - - auto elementTy = baseObjTy->getArrayElementType(); + // Attempt to infer the result type of a stdlib collection subscript. + if (isa(anchor)) + outputTy = inferCollectionSubscriptResultType(baseTy, argList); - if (!elementTy) - elementTy = baseObjTy->getInlineArrayElementType(); - - if (elementTy) { - - if (auto arraySliceTy = - dyn_cast(baseObjTy.getPointer())) { - baseObjTy = arraySliceTy->getDesugaredType(); - } - - if (argList->isUnlabeledUnary() && - isa(argList->getExpr(0))) { - - outputTy = elementTy; - - if (isLValueBase) - outputTy = LValueType::get(outputTy); - } - } else if (auto dictTy = CS.isDictionaryType(baseObjTy)) { - auto keyTy = dictTy->first; - auto valueTy = dictTy->second; - - if (argList->isUnlabeledUnary()) { - auto argTy = CS.getType(argList->getExpr(0)); - if (isFavoredParamAndArg(CS, keyTy, argTy)) { - outputTy = OptionalType::get(valueTy); - if (isLValueBase) - outputTy = LValueType::get(outputTy); - } - } - } - } - if (outputTy.isNull()) { outputTy = CS.createTypeVariable(resultLocator, TVO_CanBindToLValue | TVO_CanBindToNoEscape); diff --git a/lib/Sema/CSOptimizer.cpp b/lib/Sema/CSOptimizer.cpp new file mode 100644 index 0000000000000..5f02864239c16 --- /dev/null +++ b/lib/Sema/CSOptimizer.cpp @@ -0,0 +1,1921 @@ +//===--- CSOptimizer.cpp - Constraint Optimizer ---------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file implements disjunction and other constraint optimizations. +// +//===----------------------------------------------------------------------===// + +#include "TypeChecker.h" +#include "swift/AST/ConformanceLookup.h" +#include "swift/AST/ExistentialLayout.h" +#include "swift/AST/GenericSignature.h" +#include "swift/Basic/Defer.h" +#include "swift/Basic/OptionSet.h" +#include "swift/Sema/ConstraintGraph.h" +#include "swift/Sema/ConstraintSystem.h" +#include "llvm/ADT/BitVector.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/PointerIntPair.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/TinyPtrVector.h" +#include "llvm/Support/SaveAndRestore.h" +#include "llvm/Support/raw_ostream.h" +#include +#include + +using namespace swift; +using namespace constraints; + +namespace { + +struct DisjunctionInfo { + /// The score of the disjunction is the highest score from its choices. + /// If the score is nullopt it means that the disjunction is not optimizable. + std::optional Score; + + /// The highest scoring choices that could be favored when disjunction + /// is attempted. + llvm::TinyPtrVector FavoredChoices; + + /// Whether the decisions were based on speculative information + /// i.e. literal argument candidates or initializer type inference. + bool IsSpeculative; + + DisjunctionInfo() = default; + DisjunctionInfo(std::optional score, + ArrayRef favoredChoices, bool speculative) + : Score(score), FavoredChoices(favoredChoices), + IsSpeculative(speculative) {} + + static DisjunctionInfo none() { return {std::nullopt, {}, false}; } +}; + +class DisjunctionInfoBuilder { + std::optional Score; + SmallVector FavoredChoices; + bool IsSpeculative; + +public: + DisjunctionInfoBuilder(std::optional score) + : DisjunctionInfoBuilder(score, {}) {} + + DisjunctionInfoBuilder(std::optional score, + ArrayRef favoredChoices) + : Score(score), + FavoredChoices(favoredChoices.begin(), favoredChoices.end()), + IsSpeculative(false) {} + + void setFavoredChoices(ArrayRef choices) { + FavoredChoices.clear(); + FavoredChoices.append(choices.begin(), choices.end()); + } + + void addFavoredChoice(Constraint *constraint) { + FavoredChoices.push_back(constraint); + } + + void setSpeculative(bool value = true) { IsSpeculative = value; } + + DisjunctionInfo build() { return {Score, FavoredChoices, IsSpeculative}; } +}; + +static DeclContext *getDisjunctionDC(Constraint *disjunction) { + auto *choice = disjunction->getNestedConstraints()[0]; + switch (choice->getKind()) { + case ConstraintKind::BindOverload: + case ConstraintKind::ValueMember: + case ConstraintKind::UnresolvedValueMember: + case ConstraintKind::ValueWitness: + return choice->getDeclContext(); + default: + return nullptr; + } +} + +/// Determine whether the given disjunction appears in a context +/// transformed by a result builder. +static bool isInResultBuilderContext(ConstraintSystem &cs, + Constraint *disjunction) { + auto *DC = getDisjunctionDC(disjunction); + if (!DC) + return false; + + do { + auto fnContext = AnyFunctionRef::fromDeclContext(DC); + if (!fnContext) + return false; + + if (cs.getAppliedResultBuilderTransform(*fnContext)) + return true; + + } while ((DC = DC->getParent())); + + return false; +} + +/// If the given operator disjunction appears in some position +// inside of a not yet resolved call i.e. `a.b(1 + c(4) - 1)` +// both `+` and `-` are "in" argument context of `b`. +static bool isOperatorPassedToUnresolvedCall(ConstraintSystem &cs, + Constraint *disjunction) { + ASSERT(isOperatorDisjunction(disjunction)); + + auto *curr = castToExpr(disjunction->getLocator()->getAnchor()); + while (auto *parent = cs.getParentExpr(curr)) { + SWIFT_DEFER { curr = parent; }; + + switch (parent->getKind()) { + case ExprKind::OptionalEvaluation: + case ExprKind::Paren: + case ExprKind::Binary: + case ExprKind::PrefixUnary: + case ExprKind::PostfixUnary: + continue; + + // a.b(<> ? <> : <<...>>) + case ExprKind::Ternary: { + auto *T = cast(parent); + // If the operator is located in the condition it's + // not tied to the context. + if (T->getCondExpr() == curr) + return false; + + // But the branches are connected to the context. + continue; + } + + // Handles `a(<>), `a[<>]`, + // `.a(<>)` etc. + case ExprKind::Call: { + auto *call = cast(parent); + + // Type(...) + if (isa(call->getFn())) { + auto *ctorLoc = cs.getConstraintLocator( + call, {LocatorPathElt::ApplyFunction(), + LocatorPathElt::ConstructorMember()}); + return !cs.findSelectedOverloadFor(ctorLoc); + } + + // Ignore injected result builder methods like `buildExpression` + // and `buildBlock`. + if (auto *UDE = dyn_cast(call->getFn())) { + if (isResultBuilderMethodReference(cs.getASTContext(), UDE)) + return false; + } + + return !cs.findSelectedOverloadFor(call->getFn()); + } + + default: + return false; + } + } + + return false; +} + +// TODO: both `isIntegerType` and `isFloatType` should be available on Type +// as `isStdlib{Integer, Float}Type`. + +static bool isIntegerType(Type type) { + return type->isInt() || type->isInt8() || type->isInt16() || + type->isInt32() || type->isInt64() || type->isUInt() || + type->isUInt8() || type->isUInt16() || type->isUInt32() || + type->isUInt64(); +} + +static bool isFloatType(Type type) { + return type->isFloat() || type->isDouble() || type->isFloat80(); +} + +static bool isUnboundArrayType(Type type) { + if (auto *UGT = type->getAs()) + return UGT->getDecl() == type->getASTContext().getArrayDecl(); + return false; +} + +static bool isUnboundDictionaryType(Type type) { + if (auto *UGT = type->getAs()) + return UGT->getDecl() == type->getASTContext().getDictionaryDecl(); + return false; +} + +static bool isSupportedOperator(Constraint *disjunction) { + if (!isOperatorDisjunction(disjunction)) + return false; + + auto choices = disjunction->getNestedConstraints(); + auto *decl = getOverloadChoiceDecl(choices.front()); + + auto name = decl->getBaseIdentifier(); + if (name.isArithmeticOperator() || name.isStandardComparisonOperator() || + name.isBitwiseOperator() || name.isNilCoalescingOperator()) { + return true; + } + + // Operators like &<<, &>>, &+, .== etc. + if (llvm::any_of(choices, [](Constraint *choice) { + return isSIMDOperator(getOverloadChoiceDecl(choice)); + })) { + return true; + } + + return false; +} + +static bool isSupportedSpecialConstructor(ConstructorDecl *ctor) { + if (auto *selfDecl = ctor->getImplicitSelfDecl()) { + auto selfTy = selfDecl->getInterfaceType(); + /// Support `Int*`, `Float*` and `Double` initializers since their generic + /// overloads are not too complicated. + return selfTy && (isIntegerType(selfTy) || isFloatType(selfTy)); + } + return false; +} + +static bool isStandardComparisonOperator(Constraint *disjunction) { + auto *choice = disjunction->getNestedConstraints()[0]; + if (auto *decl = getOverloadChoiceDecl(choice)) + return decl->isOperator() && + decl->getBaseIdentifier().isStandardComparisonOperator(); + return false; +} + +static bool isOperatorNamed(Constraint *disjunction, StringRef name) { + auto *choice = disjunction->getNestedConstraints()[0]; + if (auto *decl = getOverloadChoiceDecl(choice)) + return decl->isOperator() && decl->getBaseIdentifier().is(name); + return false; +} + +static bool isArithmeticOperator(ValueDecl *decl) { + return decl->isOperator() && decl->getBaseIdentifier().isArithmeticOperator(); +} + +/// Generic choices are supported only if they are not complex enough +/// that would they'd require solving to figure out whether they are a +/// potential match or not. +static bool isSupportedGenericOverloadChoice(ValueDecl *decl, + GenericFunctionType *choiceType) { + // Same type requirements cannot be handled because each + // candidate-parameter pair is (currently) considered in isolation. + if (llvm::any_of(choiceType->getRequirements(), [](const Requirement &req) { + switch (req.getKind()) { + case RequirementKind::SameType: + case RequirementKind::SameShape: + return true; + + case RequirementKind::Conformance: + case RequirementKind::Superclass: + case RequirementKind::Layout: + return false; + } + })) + return false; + + // If there are no same-type requirements, allow signatures + // that use only concrete types or generic parameters directly + // in their parameter positions i.e. `(T, Int)`. + + auto *paramList = decl->getParameterList(); + if (!paramList) + return false; + + return llvm::all_of(paramList->getArray(), [](const ParamDecl *P) { + auto paramType = P->getInterfaceType(); + return paramType->is() || + !paramType->hasTypeParameter(); + }); +} + +static bool isSupportedDisjunction(Constraint *disjunction) { + auto choices = disjunction->getNestedConstraints(); + + if (isOperatorDisjunction(disjunction)) + return isSupportedOperator(disjunction); + + if (auto *ctor = dyn_cast_or_null( + getOverloadChoiceDecl(choices.front()))) { + if (isSupportedSpecialConstructor(ctor)) + return true; + } + + // Non-operator disjunctions are supported only if they don't + // have any complex generic choices. + return llvm::all_of(choices, [&](Constraint *choice) { + if (choice->isDisabled()) + return true; + + if (choice->getKind() != ConstraintKind::BindOverload) + return false; + + if (auto *decl = getOverloadChoiceDecl(choice)) { + // Cannot optimize declarations that return IUO because + // they form a disjunction over a result type once attempted. + if (decl->isImplicitlyUnwrappedOptional()) + return false; + + auto choiceType = decl->getInterfaceType()->getAs(); + if (!choiceType || choiceType->hasError()) + return false; + + // Non-generic choices are always supported. + if (choiceType->is()) + return true; + + if (auto *genericFn = choiceType->getAs()) + return isSupportedGenericOverloadChoice(decl, genericFn); + + return false; + } + + return false; + }); +} + +/// Determine whether the given overload choice constitutes a +/// valid choice that would be attempted during normal solving +/// without any score increases. +static ValueDecl *isViableOverloadChoice(ConstraintSystem &cs, + Constraint *constraint, + ConstraintLocator *locator) { + if (constraint->isDisabled()) + return nullptr; + + if (constraint->getKind() != ConstraintKind::BindOverload) + return nullptr; + + auto choice = constraint->getOverloadChoice(); + auto *decl = choice.getDeclOrNull(); + if (!decl) + return nullptr; + + // Ignore declarations that come from implicitly imported modules + // when `MemberImportVisibility` feature is enabled otherwise + // we might end up favoring an overload that would be diagnosed + // as unavailable later. + if (cs.getASTContext().LangOpts.hasFeature(Feature::MemberImportVisibility)) { + if (auto *useDC = constraint->getDeclContext()) { + if (!useDC->isDeclImported(decl)) + return nullptr; + } + } + + // If disjunction choice is unavailable we cannot + // do anything with it. + if (cs.isDeclUnavailable(decl, locator)) + return nullptr; + + return decl; +} + +/// Given the type variable that represents a result type of a +/// function call, check whether that call is to an initializer +/// and based on that deduce possible type for the result. +/// +/// @return A type and a flag that indicates whether there +/// are any viable failable overloads and empty pair if the +/// type variable isn't a result of an initializer call. +static llvm::PointerIntPair +inferTypeFromInitializerResultType(ConstraintSystem &cs, + TypeVariableType *typeVar, + ArrayRef disjunctions) { + assert(typeVar->getImpl().isFunctionResult()); + + auto *resultLoc = typeVar->getImpl().getLocator(); + auto *call = getAsExpr(resultLoc->getAnchor()); + if (!call) + return {}; + + auto *fn = call->getFn()->getSemanticsProvidingExpr(); + + Type instanceTy; + ConstraintLocator *ctorLocator = nullptr; + if (auto *typeExpr = getAsExpr(fn)) { + instanceTy = cs.getType(typeExpr)->getMetatypeInstanceType(); + ctorLocator = + cs.getConstraintLocator(call, {LocatorPathElt::ApplyFunction(), + LocatorPathElt::ConstructorMember()}); + } else if (auto *UDE = getAsExpr(fn)) { + if (!UDE->getName().getBaseName().isConstructor()) + return {}; + instanceTy = cs.getType(UDE->getBase())->getMetatypeInstanceType(); + ctorLocator = cs.getConstraintLocator(UDE, LocatorPathElt::Member()); + } + + if (!instanceTy || !ctorLocator) + return {}; + + auto initRef = + llvm::find_if(disjunctions, [&ctorLocator](Constraint *disjunction) { + return disjunction->getLocator() == ctorLocator; + }); + + if (initRef == disjunctions.end()) + return {}; + + unsigned numFailable = 0; + unsigned total = 0; + for (auto *choice : (*initRef)->getNestedConstraints()) { + auto *decl = isViableOverloadChoice(cs, choice, ctorLocator); + if (!decl || !isa(decl)) + continue; + + auto *ctor = cast(decl); + if (ctor->isFailable()) + ++numFailable; + + ++total; + } + + if (numFailable > 0) { + // If all of the active choices are failable, produce an optional + // type only. + if (numFailable == total) + return {instanceTy->wrapInOptionalType(), /*hasFailable=*/false}; + // Otherwise there are two options. + return {instanceTy, /*hasFailable*/ true}; + } + + return {instanceTy, /*hasFailable=*/false}; +} + +/// If the given expression represents a chain of operators that have +/// only declaration/member references and/or literals as arguments, +/// attempt to deduce a potential type of the chain. For example if +/// chain has only integral literals it's going to be `Int`, if there +/// are some floating-point literals mixed in - it's going to be `Double`. +static Type inferTypeOfArithmeticOperatorChain(ConstraintSystem &cs, + ASTNode node) { + class OperatorChainAnalyzer : public ASTWalker { + ASTContext &C; + DeclContext *DC; + ConstraintSystem &CS; + + llvm::SmallPtrSet, 2> candidates; + + bool unsupported = false; + + PreWalkResult walkToExprPre(Expr *expr) override { + if (isa(expr)) + return Action::Continue(expr); + + if (isa(expr) || isa(expr)) + return Action::Continue(expr); + + if (isa(expr)) + return Action::Continue(expr); + + // This inference works only with arithmetic operators + // because we know the structure of their overloads. + if (auto *ODRE = dyn_cast(expr)) { + if (auto *choice = ODRE->getDecls().front()) { + if (choice->getBaseIdentifier().isArithmeticOperator()) + return Action::Continue(expr); + } + } + + if (auto *LE = dyn_cast(expr)) { + if (auto *P = TypeChecker::getLiteralProtocol(C, LE)) { + if (auto defaultTy = TypeChecker::getDefaultType(P, DC)) { + addCandidateType(defaultTy, /*literal=*/true); + // String interpolation expressions have `TapExpr` + // as their children, no reason to walk them. + return Action::SkipChildren(expr); + } + } + } + + if (auto *UDE = dyn_cast(expr)) { + auto memberTy = CS.getType(UDE); + if (!memberTy->hasTypeVariable()) { + addCandidateType(memberTy, /*literal=*/false); + return Action::SkipChildren(expr); + } + } + + if (auto *DRE = dyn_cast(expr)) { + auto declTy = CS.getType(DRE); + if (!declTy->hasTypeVariable()) { + addCandidateType(declTy, /*literal=*/false); + return Action::SkipChildren(expr); + } + } + + unsupported = true; + return Action::Stop(); + } + + void addCandidateType(Type type, bool literal) { + if (literal) { + if (type->isInt()) { + // Floating-point types always subsume Int in operator chains. + if (llvm::any_of(candidates, [](const auto &candidate) { + auto ty = candidate.getPointer(); + return isFloatType(ty) || ty->isCGFloat(); + })) + return; + } else if (isFloatType(type) || type->isCGFloat()) { + // A single use of a floating-point literal flips the + // type of the entire chain to it. + (void)candidates.erase({C.getIntType(), /*literal=*/true}); + } + } + + candidates.insert({type, literal}); + } + + public: + OperatorChainAnalyzer(ConstraintSystem &CS) + : C(CS.getASTContext()), DC(CS.DC), CS(CS) {} + + Type chainType() const { + if (unsupported) + return Type(); + return candidates.size() != 1 ? Type() + : (*candidates.begin()).getPointer(); + } + }; + + OperatorChainAnalyzer analyzer(cs); + node.walk(analyzer); + + return analyzer.chainType(); +} + +NullablePtr getApplicableFnConstraint(ConstraintGraph &CG, + Constraint *disjunction) { + auto *boundVar = disjunction->getNestedConstraints()[0] + ->getFirstType() + ->getAs(); + if (!boundVar) + return nullptr; + + auto constraints = + CG.gatherNearbyConstraints(boundVar, [](Constraint *constraint) { + return constraint->getKind() == ConstraintKind::ApplicableFunction; + }); + + if (constraints.size() != 1) + return nullptr; + + auto *applicableFn = constraints.front(); + // Unapplied disjunction could appear as a argument to applicable function, + // we are not interested in that. + return applicableFn->getSecondType()->isEqual(boundVar) ? applicableFn + : nullptr; +} + +void forEachDisjunctionChoice( + ConstraintSystem &cs, Constraint *disjunction, + llvm::function_ref + callback) { + for (auto constraint : disjunction->getNestedConstraints()) { + auto *decl = + isViableOverloadChoice(cs, constraint, disjunction->getLocator()); + if (!decl) + continue; + + Type overloadType = cs.getEffectiveOverloadType( + disjunction->getLocator(), constraint->getOverloadChoice(), + /*allowMembers=*/true, constraint->getDeclContext()); + + if (!overloadType || !overloadType->is()) + continue; + + callback(constraint, decl, overloadType->castTo()); + } +} + +static OverloadedDeclRefExpr *isOverloadedDeclRef(Constraint *disjunction) { + assert(disjunction->getKind() == ConstraintKind::Disjunction); + + auto *locator = disjunction->getLocator(); + if (locator->getPath().empty()) + return getAsExpr(locator->getAnchor()); + return nullptr; +} + +static unsigned numOverloadChoicesMatchingOnArity(OverloadedDeclRefExpr *ODRE, + ArgumentList *arguments) { + return llvm::count_if(ODRE->getDecls(), [&arguments](auto *choice) { + if (auto *paramList = choice->getParameterList()) + return arguments->size() == paramList->size(); + return false; + }); +} + +static bool isVariadicGenericOverload(ValueDecl *choice) { + auto genericContext = choice->getAsGenericContext(); + if (!genericContext) + return false; + + auto *GPL = genericContext->getGenericParams(); + if (!GPL) + return false; + + return llvm::any_of(GPL->getParams(), [&](const GenericTypeParamDecl *GP) { + return GP->isParameterPack(); + }); +} + +/// This maintains an "old hack" behavior where overloads of some +/// `OverloadedDeclRef` calls were favored purely based on number of +/// argument and (non-defaulted) parameters matching. +static void findFavoredChoicesBasedOnArity( + ConstraintSystem &cs, Constraint *disjunction, ArgumentList *argumentList, + llvm::function_ref favoredChoice) { + auto *ODRE = isOverloadedDeclRef(disjunction); + if (!ODRE) + return; + + if (numOverloadChoicesMatchingOnArity(ODRE, argumentList) > 1) + return; + + bool hasVariadicGenerics = false; + SmallVector favored; + + forEachDisjunctionChoice( + cs, disjunction, + [&](Constraint *choice, ValueDecl *decl, FunctionType *overloadType) { + if (decl->getAttrs().hasAttribute()) + return; + + if (isVariadicGenericOverload(decl)) { + hasVariadicGenerics = true; + return; + } + + if (overloadType->getNumParams() == argumentList->size() || + llvm::count_if(*decl->getParameterList(), [](auto *param) { + return !param->isDefaultArgument(); + }) == argumentList->size()) + favored.push_back(choice); + }); + + if (hasVariadicGenerics) + return; + + for (auto *choice : favored) + favoredChoice(choice); +} + +/// Preserves old behavior where, for unary calls, the solver would not previously +/// consider choices that didn't match on the number of parameters (regardless of +/// defaults and variadics) and only exact matches were favored. +static std::optional preserveFavoringOfUnlabeledUnaryArgument( + ConstraintSystem &cs, Constraint *disjunction, ArgumentList *argumentList) { + if (!argumentList->isUnlabeledUnary()) + return std::nullopt; + + if (!isExpr( + cs.getParentExpr(argumentList->getUnlabeledUnaryExpr()))) + return std::nullopt; + + // The hack rolled back favoring choices if one of the overloads was a + // protocol requirement or variadic generic. + // + // Note that it doesn't matter whether such overload choices are viable + // or not, their presence disabled this "optimization". + if (llvm::any_of(disjunction->getNestedConstraints(), [](Constraint *choice) { + auto *decl = getOverloadChoiceDecl(choice); + if (!decl) + return false; + + return isa(decl->getDeclContext()) || + (!decl->getAttrs().hasAttribute() && + isVariadicGenericOverload(decl)); + })) + return std::nullopt; + + auto ODRE = isOverloadedDeclRef(disjunction); + bool preserveFavoringOfUnlabeledUnaryArgument = + !ODRE || numOverloadChoicesMatchingOnArity(ODRE, argumentList) < 2; + + if (!preserveFavoringOfUnlabeledUnaryArgument) + return std::nullopt; + + auto *argument = + argumentList->getUnlabeledUnaryExpr()->getSemanticsProvidingExpr(); + // The hack operated on "favored" types and only declaration references, + // applications, and (dynamic) subscripts had them if they managed to + // get an overload choice selected during constraint generation. + // It's sometimes possible to infer a type of a literal and an operator + // chain, so it should be allowed as well. + if (!(isExpr(argument) || isExpr(argument) || + isExpr(argument) || + isExpr(argument) || + isExpr(argument) || isExpr(argument))) + return DisjunctionInfo::none(); + + auto argumentType = cs.getType(argument)->getRValueType(); + + // For chains like `1 + 2 * 3` it's easy to deduce the type because + // we know what literal types are preferred. + if (isa(argument)) { + auto chainTy = inferTypeOfArithmeticOperatorChain(cs, argument); + if (!chainTy) + return DisjunctionInfo::none(); + + argumentType = chainTy; + } + + // Use default type of a literal (when available) to make a guess. + // This is what old hack used to do as well. + if (auto *LE = dyn_cast(argument)) { + auto *P = TypeChecker::getLiteralProtocol(cs.getASTContext(), LE); + if (!P) + return DisjunctionInfo::none(); + + auto defaultTy = TypeChecker::getDefaultType(P, cs.DC); + if (!defaultTy) + return DisjunctionInfo::none(); + + argumentType = defaultTy; + } + + ASSERT(argumentType); + + if (argumentType->hasTypeVariable() || argumentType->hasDependentMember()) + return DisjunctionInfo::none(); + + SmallVector favoredChoices; + forEachDisjunctionChoice( + cs, disjunction, + [&argumentType, &favoredChoices, &argument]( + Constraint *choice, ValueDecl *decl, FunctionType *overloadType) { + if (decl->getAttrs().hasAttribute()) + return; + + if (overloadType->getNumParams() != 1) + return; + + auto ¶m = overloadType->getParams()[0]; + + // Literals are speculative, let's not attempt to apply them too + // eagerly. + if (!param.getParameterFlags().isNone() && + (isa(argument) || isa(argument))) + return; + + if (argumentType->isEqual(param.getPlainType())) + favoredChoices.push_back(choice); + }); + + return DisjunctionInfoBuilder(/*score=*/favoredChoices.empty() ? 0 : 1, + favoredChoices) + .build(); +} + +} // end anonymous namespace + +/// Given a set of disjunctions, attempt to determine +/// favored choices in the current context. +static void determineBestChoicesInContext( + ConstraintSystem &cs, SmallVectorImpl &disjunctions, + llvm::DenseMap &result) { + double bestOverallScore = 0.0; + + auto recordResult = [&bestOverallScore, &result](Constraint *disjunction, + DisjunctionInfo &&info) { + bestOverallScore = std::max(bestOverallScore, info.Score.value_or(0)); + result.try_emplace(disjunction, info); + }; + + for (auto *disjunction : disjunctions) { + // If this is a compiler synthesized disjunction, mark it as supported + // and record all of the previously favored choices. Such disjunctions + // include - explicit coercions, IUO references,injected implicit + // initializers for CGFloat<->Double conversions and restrictions with + // multiple choices. + if (disjunction->countFavoredNestedConstraints() > 0) { + DisjunctionInfoBuilder info(/*score=*/2.0); + for (auto *choice : disjunction->getNestedConstraints()) { + if (choice->isFavored()) + info.addFavoredChoice(choice); + } + recordResult(disjunction, info.build()); + continue; + } + + auto applicableFn = + getApplicableFnConstraint(cs.getConstraintGraph(), disjunction); + + if (applicableFn.isNull()) { + auto *locator = disjunction->getLocator(); + if (auto expr = getAsExpr(locator->getAnchor())) { + auto *parentExpr = cs.getParentExpr(expr); + // Look through optional evaluation, so + // we can cover expressions like `a?.b + 2`. + if (isExpr(parentExpr)) + parentExpr = cs.getParentExpr(parentExpr); + + if (parentExpr) { + // If this is a chained member reference or a direct operator + // argument it could be prioritized since it helps to establish + // context for other calls i.e. `(a.)b + 2` if `a` and/or `b` + // are disjunctions they should be preferred over `+`. + switch (parentExpr->getKind()) { + case ExprKind::Binary: + case ExprKind::PrefixUnary: + case ExprKind::PostfixUnary: + case ExprKind::UnresolvedDot: { + llvm::SmallVector favoredChoices; + // Favor choices that don't require application. + llvm::copy_if( + disjunction->getNestedConstraints(), + std::back_inserter(favoredChoices), [](Constraint *choice) { + auto *decl = getOverloadChoiceDecl(choice); + return decl && + !decl->getInterfaceType()->is(); + }); + recordResult( + disjunction, + DisjunctionInfoBuilder(/*score=*/1.0, favoredChoices).build()); + continue; + } + + default: + break; + } + } + } + + continue; + } + + auto argFuncType = + applicableFn.get()->getFirstType()->getAs(); + + auto argumentList = cs.getArgumentList(applicableFn.get()->getLocator()); + if (!argumentList) + return; + + for (const auto &argument : *argumentList) { + if (auto *expr = argument.getExpr()) { + // Directly `<#...#>` or has one inside. + if (isa(expr) || + cs.containsIDEInspectionTarget(expr)) + return; + } + } + + // This maintains an "old hack" behavior where overloads + // of `OverloadedDeclRef` calls were favored purely + // based on arity of arguments and parameters matching. + { + llvm::TinyPtrVector favoredChoices; + findFavoredChoicesBasedOnArity(cs, disjunction, argumentList, + [&favoredChoices](Constraint *choice) { + favoredChoices.push_back(choice); + }); + + if (!favoredChoices.empty()) { + recordResult( + disjunction, + DisjunctionInfoBuilder(/*score=*/0.01, favoredChoices).build()); + continue; + } + } + + // Preserves old behavior where, for unary calls, the solver + // would not consider choices that didn't match on the number + // of parameters (regardless of defaults) and only exact + // matches were favored. + if (auto info = preserveFavoringOfUnlabeledUnaryArgument(cs, disjunction, + argumentList)) { + recordResult(disjunction, std::move(info.value())); + continue; + } + + if (!isSupportedDisjunction(disjunction)) + continue; + + SmallVector argsWithLabels; + { + argsWithLabels.append(argFuncType->getParams().begin(), + argFuncType->getParams().end()); + FunctionType::relabelParams(argsWithLabels, argumentList); + } + + struct ArgumentCandidate { + Type type; + // The candidate type is derived from a literal expression. + bool fromLiteral : 1; + // The candidate type is derived from a call to an + // initializer i.e. `Double(...)`. + bool fromInitializerCall : 1; + + ArgumentCandidate(Type type, bool fromLiteral = false, + bool fromInitializerCall = false) + : type(type), fromLiteral(fromLiteral), + fromInitializerCall(fromInitializerCall) {} + }; + + // Determine whether there are any non-speculative choices + // in the given set of candidates. Speculative choices are + // literals or types inferred from initializer calls. + auto anyNonSpeculativeCandidates = + [&](ArrayRef candidates) { + // If there is only one (non-CGFloat) candidate inferred from + // an initializer call we don't consider this a speculation. + // + // CGFloat inference is always speculative because of the + // implicit conversion between Double and CGFloat. + if (llvm::count_if(candidates, [&](const auto &candidate) { + return candidate.fromInitializerCall && + !candidate.type->isCGFloat(); + }) == 1) + return true; + + // If there are no non-literal and non-initializer-inferred types + // in the list, consider this is a speculation. + return llvm::any_of(candidates, [&](const auto &candidate) { + return !candidate.fromLiteral && !candidate.fromInitializerCall; + }); + }; + + auto anyNonSpeculativeResultTypes = [](ArrayRef results) { + return llvm::any_of(results, [](Type resultTy) { + // Double and CGFloat are considered speculative because + // there exists an implicit conversion between them and + // preference is based on score impact in the overall solution. + return !(resultTy->isDouble() || resultTy->isCGFloat()); + }); + }; + + SmallVector, 2> + argumentCandidates; + argumentCandidates.resize(argFuncType->getNumParams()); + + llvm::TinyPtrVector resultTypes; + + bool hasArgumentCandidates = false; + bool isOperator = isOperatorDisjunction(disjunction); + + for (unsigned i = 0, n = argFuncType->getNumParams(); i != n; ++i) { + const auto ¶m = argFuncType->getParams()[i]; + auto argType = cs.simplifyType(param.getPlainType()); + + SmallVector optionals; + // i.e. `??` operator could produce an optional type + // so `test(<> ?? 0) could result in an optional + // argument that wraps a type variable. It should be possible + // to infer bindings from underlying type variable and restore + // optionality. + if (argType->hasTypeVariable()) { + if (auto *typeVar = argType->lookThroughAllOptionalTypes(optionals) + ->getAs()) + argType = typeVar; + } + + SmallVector types; + if (auto *typeVar = argType->getAs()) { + auto bindingSet = cs.getBindingsFor(typeVar); + + // We need to have a notion of "complete" binding set before + // we can allow inference from generic parameters and ternary, + // otherwise we'd make a favoring decision that might not be + // correct i.e. `v ?? (<> ? nil : o)` where `o` is `Int`. + // `getBindingsFor` doesn't currently infer transitive bindings + // which means that for a ternary we'd only have a single + // binding - `Int` which could lead to favoring overload of + // `??` and has non-optional parameter on the right-hand side. + if (typeVar->getImpl().getGenericParameter() || + typeVar->getImpl().isTernary()) + continue; + + auto restoreOptionality = [](Type type, unsigned numOptionals) { + for (unsigned i = 0; i != numOptionals; ++i) + type = type->wrapInOptionalType(); + return type; + }; + + for (const auto &binding : bindingSet.Bindings) { + auto type = restoreOptionality(binding.BindingType, optionals.size()); + types.push_back({type}); + } + + for (const auto &literal : bindingSet.Literals) { + if (literal.second.hasDefaultType()) { + // Add primary default type + auto type = restoreOptionality(literal.second.getDefaultType(), + optionals.size()); + types.push_back({type, + /*fromLiteral=*/true}); + } else if (literal.first == + cs.getASTContext().getProtocol( + KnownProtocolKind::ExpressibleByNilLiteral) && + literal.second.IsDirectRequirement) { + // `==` and `!=` operators have special overloads that accept `nil` + // as `_OptionalNilComparisonType` which is preferred over a + // generic form `(T?, T?)`. + if (isOperatorNamed(disjunction, "==") || + isOperatorNamed(disjunction, "!=")) { + auto nilComparisonTy = + cs.getASTContext().get_OptionalNilComparisonTypeType(); + types.push_back({nilComparisonTy, /*fromLiteral=*/true}); + } + } + } + + // Help situations like `1 + {Double, CGFloat}(...)` by inferring + // a type for the second operand of `+` based on a type being + // constructed. + if (typeVar->getImpl().isFunctionResult()) { + auto *resultLoc = typeVar->getImpl().getLocator(); + + if (auto type = inferTypeOfArithmeticOperatorChain( + cs, resultLoc->getAnchor())) { + types.push_back({type, /*fromLiteral=*/true}); + } + + auto binding = + inferTypeFromInitializerResultType(cs, typeVar, disjunctions); + + if (auto instanceTy = binding.getPointer()) { + types.push_back({instanceTy, + /*fromLiteral=*/false, + /*fromInitializerCall=*/true}); + + if (binding.getInt()) + types.push_back({instanceTy->wrapInOptionalType(), + /*fromLiteral=*/false, + /*fromInitializerCall=*/true}); + } + } + } else { + types.push_back({argType, /*fromLiteral=*/false}); + } + + argumentCandidates[i].append(types); + hasArgumentCandidates |= !types.empty(); + } + + auto resultType = cs.simplifyType(argFuncType->getResult()); + if (auto *typeVar = resultType->getAs()) { + auto bindingSet = cs.getBindingsFor(typeVar); + + for (const auto &binding : bindingSet.Bindings) { + resultTypes.push_back(binding.BindingType); + } + + // Infer bindings for each side of a ternary condition. + bindingSet.forEachAdjacentVariable( + [&cs, &resultTypes](TypeVariableType *adjacentVar) { + auto *adjacentLoc = adjacentVar->getImpl().getLocator(); + // This is one of the sides of a ternary operator. + if (adjacentLoc->directlyAt()) { + auto adjacentBindings = cs.getBindingsFor(adjacentVar); + + for (const auto &binding : adjacentBindings.Bindings) + resultTypes.push_back(binding.BindingType); + } + }); + } else { + resultTypes.push_back(resultType); + } + + // Determine whether all of the argument candidates are speculative (i.e. + // literals). This information is going to be used later on when we need to + // decide how to score a matching choice. + bool onlySpeculativeArgumentCandidates = + hasArgumentCandidates && + llvm::none_of( + indices(argFuncType->getParams()), [&](const unsigned argIdx) { + return anyNonSpeculativeCandidates(argumentCandidates[argIdx]); + }); + + bool canUseContextualResultTypes = + isOperator && !isStandardComparisonOperator(disjunction); + + // Match arguments to the given overload choice. + auto matchArguments = [&](OverloadChoice choice, FunctionType *overloadType) + -> std::optional { + auto *decl = choice.getDeclOrNull(); + assert(decl); + + auto hasAppliedSelf = + decl->hasCurriedSelf() && + doesMemberRefApplyCurriedSelf(choice.getBaseType(), decl); + + ParameterListInfo paramListInfo(overloadType->getParams(), decl, + hasAppliedSelf); + + MatchCallArgumentListener listener; + return matchCallArguments(argsWithLabels, overloadType->getParams(), + paramListInfo, + argumentList->getFirstTrailingClosureIndex(), + /*allow fixes*/ false, listener, std::nullopt); + }; + + // Determine whether the candidate type is a subclass of the superclass + // type. + std::function isSubclassOf = [&](Type candidateType, + Type superclassType) { + // Conversion from a concrete type to its existential value. + if (superclassType->isExistentialType() && !superclassType->isAny()) { + auto layout = superclassType->getExistentialLayout(); + + if (auto layoutConstraint = layout.getLayoutConstraint()) { + if (layoutConstraint->isClass() && + !(candidateType->isClassExistentialType() || + candidateType->mayHaveSuperclass())) + return false; + } + + if (layout.explicitSuperclass && + !isSubclassOf(candidateType, layout.explicitSuperclass)) + return false; + + return llvm::all_of(layout.getProtocols(), [&](ProtocolDecl *P) { + if (auto superclass = P->getSuperclassDecl()) { + if (!isSubclassOf(candidateType, + superclass->getDeclaredInterfaceType())) + return false; + } + + auto result = TypeChecker::containsProtocol(candidateType, P, + /*allowMissing=*/false); + return result.first || result.second; + }); + } + + auto *subclassDecl = candidateType->getClassOrBoundGenericClass(); + auto *superclassDecl = superclassType->getClassOrBoundGenericClass(); + + if (!(subclassDecl && superclassDecl)) + return false; + + return superclassDecl->isSuperclassOf(subclassDecl); + }; + + enum class MatchFlag { + OnParam = 0x01, + Literal = 0x02, + ExactOnly = 0x04, + DisableCGFloatDoubleConversion = 0x08, + StringInterpolation = 0x10, + }; + + using MatchOptions = OptionSet; + + // Perform a limited set of checks to determine whether the candidate + // could possibly match the parameter type: + // + // - Equality + // - Protocol conformance(s) + // - Optional injection + // - Superclass conversion + // - Array-to-pointer conversion + // - Value to existential conversion + // - Exact match on top-level types + // + // In situations when it's not possible to determine whether a candidate + // type matches a parameter type (i.e. when partially resolved generic + // types are matched) this function is going to produce \c std::nullopt + // instead of `0` that indicates "not a match". + std::function(GenericSignature, ValueDecl *, Type, + Type, MatchOptions)> + scoreCandidateMatch = + [&](GenericSignature genericSig, ValueDecl *choice, + Type candidateType, Type paramType, + MatchOptions options) -> std::optional { + auto areEqual = [&](Type a, Type b) { + return a->getDesugaredType()->isEqual(b->getDesugaredType()); + }; + + auto isCGFloatDoubleConversionSupported = [&options]() { + // CGFloat <-> Double conversion is supposed only while + // match argument candidates to parameters. + return options.contains(MatchFlag::OnParam) && + !options.contains(MatchFlag::DisableCGFloatDoubleConversion); + }; + + // Allow CGFloat -> Double widening conversions between + // candidate argument types and parameter types. This would + // make sure that Double is always preferred over CGFloat + // when using literals and ranking supported disjunction + // choices. Narrowing conversion (Double -> CGFloat) should + // be delayed as much as possible. + if (isCGFloatDoubleConversionSupported()) { + if (candidateType->isCGFloat() && paramType->isDouble()) { + return options.contains(MatchFlag::Literal) ? 0.2 : 0.9; + } + } + + if (options.contains(MatchFlag::ExactOnly)) { + // If an exact match is requested favor only `[...]` to `Array<...>` + // since everything else is going to increase to score. + if (options.contains(MatchFlag::Literal)) { + if (isUnboundArrayType(candidateType)) + return paramType->isArray() ? 0.3 : 0; + + if (isUnboundDictionaryType(candidateType)) + return cs.isDictionaryType(paramType) ? 0.3 : 0; + } + + if (!areEqual(candidateType, paramType)) + return 0; + return options.contains(MatchFlag::Literal) ? 0.3 : 1; + } + + // Exact match between candidate and parameter types. + if (areEqual(candidateType, paramType)) { + return options.contains(MatchFlag::Literal) ? 0.3 : 1; + } + + if (options.contains(MatchFlag::Literal)) { + if (paramType->hasTypeParameter() || + paramType->isAnyExistentialType()) { + // Attempt to match literal default to generic parameter. + // This helps to determine whether there are any generic + // overloads that are a possible match. + auto score = + scoreCandidateMatch(genericSig, choice, candidateType, + paramType, options - MatchFlag::Literal); + if (score == 0) + return 0; + + // Optional injection lowers the score for operators to match + // pre-optimizer behavior. + return choice->isOperator() && paramType->getOptionalObjectType() + ? 0.2 + : 0.3; + } else { + // Integer and floating-point literals can match any parameter + // type that conforms to `ExpressibleBy{Integer, Float}Literal` + // protocol. Since this assessment is done in isolation we don't + // lower the score even though this would be a non-default binding + // for a literal. + if (candidateType->isInt() && + TypeChecker::conformsToKnownProtocol( + paramType, KnownProtocolKind::ExpressibleByIntegerLiteral)) + return 0.3; + + if (candidateType->isDouble() && + TypeChecker::conformsToKnownProtocol( + paramType, KnownProtocolKind::ExpressibleByFloatLiteral)) + return 0.3; + + if (candidateType->isBool() && + TypeChecker::conformsToKnownProtocol( + paramType, KnownProtocolKind::ExpressibleByBooleanLiteral)) + return 0.3; + + if (candidateType->isString()) { + auto literalProtocol = + options.contains(MatchFlag::StringInterpolation) + ? KnownProtocolKind::ExpressibleByStringInterpolation + : KnownProtocolKind::ExpressibleByStringLiteral; + + if (TypeChecker::conformsToKnownProtocol(paramType, + literalProtocol)) + return 0.3; + } + + auto &ctx = cs.getASTContext(); + + // Check if the other side conforms to `ExpressibleByArrayLiteral` + // protocol (in some way). We want an overly optimistic result + // here to avoid under-favoring. + if (candidateType->isArray() && + checkConformanceWithoutContext( + paramType, + ctx.getProtocol(KnownProtocolKind::ExpressibleByArrayLiteral), + /*allowMissing=*/true)) + return 0.3; + + // Check if the other side conforms to + // `ExpressibleByDictionaryLiteral` protocol (in some way). + // We want an overly optimistic result here to avoid under-favoring. + if (candidateType->isDictionary() && + checkConformanceWithoutContext( + paramType, + ctx.getProtocol( + KnownProtocolKind::ExpressibleByDictionaryLiteral), + /*allowMissing=*/true)) + return 0.3; + } + + return 0; + } + + // Check whether match would require optional injection. + { + SmallVector candidateOptionals; + SmallVector paramOptionals; + + candidateType = + candidateType->lookThroughAllOptionalTypes(candidateOptionals); + paramType = paramType->lookThroughAllOptionalTypes(paramOptionals); + + if (!candidateOptionals.empty() || !paramOptionals.empty()) { + // Can match i.e. Int? to Int or T to Int? + if ((paramOptionals.empty() && + paramType->is()) || + paramOptionals.size() >= candidateOptionals.size()) { + auto score = scoreCandidateMatch(genericSig, choice, candidateType, + paramType, options); + // Injection lowers the score slightly to comply with + // old behavior where exact matches on operator parameter + // types were always preferred. + return score > 0 && choice->isOperator() ? score.value() - 0.1 + : score; + } + + // Optionality mismatch. + return 0; + } + } + + // Candidate could be converted to a superclass. + if (isSubclassOf(candidateType, paramType)) + return 1; + + // Possible Array -> Unsafe*Pointer conversion. + if (options.contains(MatchFlag::OnParam)) { + if (candidateType->isArray() && + paramType->getAnyPointerElementType()) + return 1; + } + + // If both argument and parameter are tuples of the same arity, + // it's a match. + { + if (auto *candidateTuple = candidateType->getAs()) { + auto *paramTuple = paramType->getAs(); + if (paramTuple && + candidateTuple->getNumElements() == paramTuple->getNumElements()) + return 1; + } + } + + // Check protocol requirement(s) if this parameter is a + // generic parameter type. + if (genericSig && paramType->isTypeParameter()) { + // Light-weight check if cases where `checkRequirements` is not + // applicable. + auto checkProtocolRequirementsOnly = [&]() -> double { + auto protocolRequirements = + genericSig->getRequiredProtocols(paramType); + if (llvm::all_of(protocolRequirements, [&](ProtocolDecl *protocol) { + return bool(cs.lookupConformance(candidateType, protocol)); + })) { + if (auto *GP = paramType->getAs()) { + auto *paramDecl = GP->getDecl(); + if (paramDecl && paramDecl->isOpaqueType()) + return 1.0; + } + return 0.7; + } + + return 0; + }; + + // If candidate is not fully resolved or is matched against a + // dependent member type (i.e. `Self.T`), let's check conformances + // only and lower the score. + if (candidateType->hasTypeVariable() || + candidateType->hasUnboundGenericType() || + paramType->is()) { + return checkProtocolRequirementsOnly(); + } + + // Cannot match anything but generic type parameters here. + if (!paramType->is()) + return std::nullopt; + + bool hasUnsatisfiableRequirements = false; + SmallVector requirements; + + for (const auto &requirement : genericSig.getRequirements()) { + if (hasUnsatisfiableRequirements) + break; + + llvm::SmallPtrSet toExamine; + + auto recordReferencesGenericParams = [&toExamine](Type type) { + type.visit([&toExamine](Type innerTy) { + if (auto *GP = innerTy->getAs()) + toExamine.insert(GP); + }); + }; + + recordReferencesGenericParams(requirement.getFirstType()); + + if (requirement.getKind() != RequirementKind::Layout) + recordReferencesGenericParams(requirement.getSecondType()); + + if (llvm::any_of(toExamine, [&](GenericTypeParamType *GP) { + return paramType->isEqual(GP); + })) { + requirements.push_back(requirement); + // If requirement mentions other generic parameters + // `checkRequirements` would because we don't have + // candidate substitutions for anything but the current + // parameter type. + hasUnsatisfiableRequirements |= toExamine.size() > 1; + } + } + + // If there are no requirements associated with the generic + // parameter or dependent member type it could match any type. + if (requirements.empty()) + return 0.7; + + // If some of the requirements cannot be satisfied, because + // they reference other generic parameters, for example: + // ``, let's perform a + // light-weight check instead of skipping this overload choice. + if (hasUnsatisfiableRequirements) + return checkProtocolRequirementsOnly(); + + // If the candidate type is fully resolved, let's check all of + // the requirements that are associated with the corresponding + // parameter, if all of them are satisfied this candidate is + // an exact match. + auto result = checkRequirements( + requirements, + [¶mType, &candidateType](SubstitutableType *type) -> Type { + if (type->isEqual(paramType)) + return candidateType; + return ErrorType::get(type); + }, + SubstOptions(std::nullopt)); + + // Concrete operator overloads are always more preferable to + // generic ones if there are exact or subtype matches, for + // everything else the solver should try both concrete and + // generic and disambiguate during ranking. + if (result == CheckRequirementsResult::Success) + return choice->isOperator() ? 0.9 : 1.0; + + return 0; + } + + // Parameter is generic, let's check whether top-level + // types match i.e. Array as a parameter. + // + // This is slightly better than all of the conformances matching + // because the parameter is concrete and could split the system. + if (paramType->hasTypeParameter()) { + auto *candidateDecl = candidateType->getAnyNominal(); + auto *paramDecl = paramType->getAnyNominal(); + + // Conservatively we need to make sure that this is not worse + // than matching against a generic parameter with or without + // requirements. + if (candidateDecl && paramDecl && candidateDecl == paramDecl) { + // If the candidate it not yet fully resolved, let's lower the + // score slightly to avoid over-favoring generic overload choices. + if (candidateType->hasTypeVariable()) + return 0.8; + + // If the candidate is fully resolved we need to treat this + // as we would generic parameter otherwise there is a risk + // of skipping some of the valid choices. + return choice->isOperator() ? 0.9 : 1.0; + } + } + + return 0; + }; + + // The choice with the best score. + double bestScore = 0.0; + SmallVector, 2> favoredChoices; + + forEachDisjunctionChoice( + cs, disjunction, + [&](Constraint *choice, ValueDecl *decl, FunctionType *overloadType) { + GenericSignature genericSig; + { + if (auto *GF = dyn_cast(decl)) { + genericSig = GF->getGenericSignature(); + } else if (auto *SD = dyn_cast(decl)) { + genericSig = SD->getGenericSignature(); + } + } + + auto matchings = + matchArguments(choice->getOverloadChoice(), overloadType); + if (!matchings) + return; + + // Require exact matches only if all of the arguments + // are literals and there are no usable contextual result + // types that could help narrow favored choices. + bool favorExactMatchesOnly = + onlySpeculativeArgumentCandidates && + (!canUseContextualResultTypes || resultTypes.empty()); + + // This is important for SIMD operators in particular because + // a lot of their overloads have same-type requires to a concrete + // type: `(_: SIMD*, ...) -> ...`. + if (genericSig) { + overloadType = overloadType->getReducedType(genericSig) + ->castTo(); + } + + double score = 0.0; + unsigned numDefaulted = 0; + for (unsigned paramIdx = 0, n = overloadType->getNumParams(); + paramIdx != n; ++paramIdx) { + const auto ¶m = overloadType->getParams()[paramIdx]; + + auto argIndices = matchings->parameterBindings[paramIdx]; + switch (argIndices.size()) { + case 0: + // Current parameter is defaulted, mark and continue. + ++numDefaulted; + continue; + + case 1: + // One-to-one match between argument and parameter. + break; + + default: + // Cannot deal with multiple possible matchings at the moment. + return; + } + + auto argIdx = argIndices.front(); + + // Looks like there is nothing know about the argument. + if (argumentCandidates[argIdx].empty()) + continue; + + const auto paramFlags = param.getParameterFlags(); + + // If parameter is variadic we cannot compare because we don't know + // real arity. + if (paramFlags.isVariadic()) + continue; + + auto paramType = param.getPlainType(); + + if (paramFlags.isAutoClosure()) + paramType = paramType->castTo()->getResult(); + + // FIXME: Let's skip matching function types for now + // because they have special rules for e.g. Concurrency + // (around @Sendable) and @convention(c). + if (paramType->is()) + continue; + + // The idea here is to match the parameter type against + // all of the argument candidate types and pick the best + // match (i.e. exact equality one). + // + // If none of the candidates match exactly and they are + // all bound concrete types, we consider this is mismatch + // at this parameter position and remove the overload choice + // from consideration. + double bestCandidateScore = 0; + llvm::BitVector mismatches(argumentCandidates[argIdx].size()); + + for (unsigned candidateIdx : + indices(argumentCandidates[argIdx])) { + // If one of the candidates matched exactly there is no reason + // to continue checking. + if (bestCandidateScore == 1) + break; + + auto candidate = argumentCandidates[argIdx][candidateIdx]; + + // `inout` parameter accepts only l-value argument. + if (paramFlags.isInOut() && !candidate.type->is()) { + mismatches.set(candidateIdx); + continue; + } + + MatchOptions options(MatchFlag::OnParam); + if (candidate.fromLiteral) + options |= MatchFlag::Literal; + if (favorExactMatchesOnly) + options |= MatchFlag::ExactOnly; + + // Disable CGFloat -> Double conversion for unary operators. + // + // Some of the unary operators, i.e. prefix `-`, don't have + // CGFloat variants and expect generic `FloatingPoint` overload + // to match CGFloat type. Let's not attempt `CGFloat` -> `Double` + // conversion for unary operators because it always leads + // to a worse solutions vs. generic overloads. + if (n == 1 && decl->isOperator()) + options |= MatchFlag::DisableCGFloatDoubleConversion; + + // Disable implicit CGFloat -> Double widening conversion if + // argument is an explicit call to `CGFloat` initializer. + if (candidate.type->isCGFloat() && + candidate.fromInitializerCall) + options |= MatchFlag::DisableCGFloatDoubleConversion; + + if (isExpr( + argumentList->getExpr(argIdx) + ->getSemanticsProvidingExpr())) + options |= MatchFlag::StringInterpolation; + + // The specifier for a candidate only matters for `inout` check. + auto candidateScore = scoreCandidateMatch( + genericSig, decl, candidate.type->getWithoutSpecifierType(), + paramType, options); + + if (!candidateScore) + continue; + + if (candidateScore > 0) { + bestCandidateScore = + std::max(bestCandidateScore, candidateScore.value()); + continue; + } + + if (!candidate.type->hasTypeVariable()) + mismatches.set(candidateIdx); + } + + // If none of the candidates for this parameter matched, let's + // drop this overload from any further consideration. + if (mismatches.all()) + return; + + score += bestCandidateScore; + } + + // An overload whether all of the parameters are defaulted + // that's called without arguments. + if (numDefaulted == overloadType->getNumParams()) + return; + + // Average the score to avoid disfavoring disjunctions with fewer + // parameters. + score /= (overloadType->getNumParams() - numDefaulted); + + // If one of the result types matches exactly, that's a good + // indication that overload choice should be favored. + // + // It's only safe to match result types of operators + // because regular functions/methods/subscripts and + // especially initializers could end up with a lot of + // favored overloads because on the result type alone. + if (canUseContextualResultTypes && + (score > 0 || !hasArgumentCandidates)) { + if (llvm::any_of(resultTypes, [&](const Type candidateResultTy) { + return scoreCandidateMatch(genericSig, decl, + overloadType->getResult(), + candidateResultTy, + /*options=*/{}) > 0; + })) { + score += 1.0; + } + } + + if (score > 0) { + // Nudge the score slightly to prefer concrete homogeneous + // arithmetic operators. + // + // This is an opportunistic optimization based on the operator + // use patterns where homogeneous operators are the most + // heavily used ones. + if (isArithmeticOperator(decl) && + overloadType->getNumParams() == 2) { + auto resultTy = overloadType->getResult(); + if (!resultTy->hasTypeParameter() && + llvm::all_of(overloadType->getParams(), + [&resultTy](const auto ¶m) { + return param.getPlainType()->isEqual(resultTy); + })) + score += 0.01; + } + + favoredChoices.push_back({choice, score}); + bestScore = std::max(bestScore, score); + } + }); + + if (cs.isDebugMode()) { + PrintOptions PO; + PO.PrintTypesForDebugging = true; + + llvm::errs().indent(cs.solverState->getCurrentIndent()) + << "<<< Disjunction " + << disjunction->getNestedConstraints()[0]->getFirstType()->getString( + PO) + << " with score " << bestScore << "\n"; + } + + bestOverallScore = std::max(bestOverallScore, bestScore); + + // Determine if the score and favoring decisions here are + // based only on "speculative" sources i.e. inference from + // literals. + // + // This information is going to be used by the disjunction + // selection algorithm to prevent over-eager selection of + // the operators over unsupported non-operator declarations. + bool isSpeculative = onlySpeculativeArgumentCandidates && + (!canUseContextualResultTypes || + !anyNonSpeculativeResultTypes(resultTypes)); + + DisjunctionInfoBuilder info(/*score=*/bestScore); + + info.setSpeculative(isSpeculative); + + for (const auto &choice : favoredChoices) { + if (choice.second == bestScore) + info.addFavoredChoice(choice.first); + } + + recordResult(disjunction, info.build()); + } + + if (cs.isDebugMode() && bestOverallScore > 0) { + PrintOptions PO; + PO.PrintTypesForDebugging = true; + + auto getLogger = [&](unsigned extraIndent = 0) -> llvm::raw_ostream & { + return llvm::errs().indent(cs.solverState->getCurrentIndent() + + extraIndent); + }; + + { + auto &log = getLogger(); + log << "(Optimizing disjunctions: ["; + + interleave( + disjunctions, + [&](const auto *disjunction) { + log << disjunction->getNestedConstraints()[0] + ->getFirstType() + ->getString(PO); + }, + [&]() { log << ", "; }); + + log << "]\n"; + } + + getLogger(/*extraIndent=*/4) + << "Best overall score = " << bestOverallScore << '\n'; + + for (auto *disjunction : disjunctions) { + auto &entry = result[disjunction]; + getLogger(/*extraIndent=*/4) + << "[Disjunction '" + << disjunction->getNestedConstraints()[0]->getFirstType()->getString( + PO) + << "' with score = " << entry.Score.value_or(0) << '\n'; + + for (const auto *choice : entry.FavoredChoices) { + auto &log = getLogger(/*extraIndent=*/6); + + log << "- "; + choice->print(log, &cs.getASTContext().SourceMgr); + log << '\n'; + } + + getLogger(/*extraIdent=*/4) << "]\n"; + } + + getLogger() << ")\n"; + } +} + +static std::optional isPreferable(ConstraintSystem &cs, + Constraint *disjunctionA, + Constraint *disjunctionB) { + // Consider only operator vs. non-operator situations. + if (isOperatorDisjunction(disjunctionA) == + isOperatorDisjunction(disjunctionB)) + return std::nullopt; + + // Prevent operator selection if its passed as an argument + // to not-yet resolved call. This helps to make sure that + // in result builder context chained members and other + // non-operator disjunctions are always selected first, + // because they provide the context and help to prune the system. + if (isInResultBuilderContext(cs, disjunctionA)) { + if (isOperatorDisjunction(disjunctionA)) { + if (isOperatorPassedToUnresolvedCall(cs, disjunctionA)) + return false; + } else { + if (isOperatorPassedToUnresolvedCall(cs, disjunctionB)) + return true; + } + } + + return std::nullopt; +} + +std::optional>> +ConstraintSystem::selectDisjunction() { + if (performanceHacksEnabled()) { + if (auto *disjunction = selectDisjunctionWithHacks()) + return std::make_pair(disjunction, llvm::TinyPtrVector()); + return std::nullopt; + } + + SmallVector disjunctions; + + collectDisjunctions(disjunctions); + if (disjunctions.empty()) + return std::nullopt; + + llvm::DenseMap favorings; + determineBestChoicesInContext(*this, disjunctions, favorings); + + // Pick the disjunction with the smallest number of favored, then active + // choices. + auto bestDisjunction = std::min_element( + disjunctions.begin(), disjunctions.end(), + [&](Constraint *first, Constraint *second) -> bool { + unsigned firstActive = first->countActiveNestedConstraints(); + unsigned secondActive = second->countActiveNestedConstraints(); + + if (firstActive == 1 || secondActive == 1) + return secondActive != 1; + + if (auto preference = isPreferable(*this, first, second)) + return preference.value(); + + auto &[firstScore, firstFavoredChoices, isFirstSpeculative] = + favorings[first]; + auto &[secondScore, secondFavoredChoices, isSecondSpeculative] = + favorings[second]; + + bool isFirstOperator = isOperatorDisjunction(first); + bool isSecondOperator = isOperatorDisjunction(second); + + // Not all of the non-operator disjunctions are supported by the + // ranking algorithm, so to prevent eager selection of operators + // when nothing concrete is known about them, let's reset the score + // and compare purely based on number of choices. + if (isFirstOperator != isSecondOperator) { + if (isFirstOperator && isFirstSpeculative) + firstScore = 0; + + if (isSecondOperator && isSecondSpeculative) + secondScore = 0; + } + + // Rank based on scores only if both disjunctions are supported. + if (firstScore && secondScore) { + // If both disjunctions have the same score they should be ranked + // based on number of favored/active choices. + if (*firstScore != *secondScore) + return *firstScore > *secondScore; + + // If the scores are the same and both disjunctions are operators + // they could be ranked purely based on whether the candidates + // were speculative or not. The one with more context always wins. + // + // Consider the following situation: + // + // func test(_: Int) { ... } + // func test(_: String) { ... } + // + // test("a" + "b" + "c") + // + // In this case we should always prefer ... + "c" over "a" + "b" + // because it would fail and prune the other overload if parameter + // type (aka contextual type) is `Int`. + if (isFirstOperator && isSecondOperator && + isFirstSpeculative != isSecondSpeculative) + return isSecondSpeculative; + } + + // Use favored choices only if disjunction score is higher + // than zero. This means that we can maintain favoring + // choices without impacting selection decisions. + unsigned numFirstFavored = + firstScore.value_or(0) ? firstFavoredChoices.size() : 0; + unsigned numSecondFavored = + secondScore.value_or(0) ? secondFavoredChoices.size() : 0; + + if (numFirstFavored == numSecondFavored) { + if (firstActive != secondActive) + return firstActive < secondActive; + } + + numFirstFavored = numFirstFavored ? numFirstFavored : firstActive; + numSecondFavored = numSecondFavored ? numSecondFavored : secondActive; + + return numFirstFavored < numSecondFavored; + }); + + if (bestDisjunction != disjunctions.end()) + return std::make_pair(*bestDisjunction, + favorings[*bestDisjunction].FavoredChoices); + + return std::nullopt; +} diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 786c7a8cad5f1..f511c4881c6d8 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -15184,9 +15184,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 eb8581a130752..13efcb2406db1 100644 --- a/lib/Sema/CSSolver.cpp +++ b/lib/Sema/CSSolver.cpp @@ -2011,6 +2011,29 @@ tryOptimizeGenericDisjunction(ConstraintSystem &cs, Constraint *disjunction, return nullptr; } + if (!cs.performanceHacksEnabled()) { + // 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) @@ -2287,6 +2310,8 @@ void DisjunctionChoiceProducer::partitionDisjunction( // end of the partitioning. SmallVector favored; SmallVector everythingElse; + // Disfavored choices are part of `everythingElse` but introduced at the end. + SmallVector disfavored; SmallVector simdOperators; SmallVector disabled; SmallVector unavailable; @@ -2319,6 +2344,11 @@ void DisjunctionChoiceProducer::partitionDisjunction( everythingElse.push_back(index); return true; } + + if (decl->getAttrs().hasAttribute()) { + disfavored.push_back(index); + return true; + } } return false; @@ -2362,6 +2392,9 @@ void DisjunctionChoiceProducer::partitionDisjunction( return true; }); + // Introduce disfavored choices at the end. + everythingElse.append(disfavored); + // Local function to create the next partition based on the options // passed in. PartitionAppendCallback appendPartition = @@ -2381,7 +2414,7 @@ void DisjunctionChoiceProducer::partitionDisjunction( assert(Ordering.size() == Choices.size()); } -Constraint *ConstraintSystem::selectDisjunction() { +Constraint *ConstraintSystem::selectDisjunctionWithHacks() { SmallVector disjunctions; collectDisjunctions(disjunctions); diff --git a/lib/Sema/CSStep.cpp b/lib/Sema/CSStep.cpp index eb8dd95741603..45c2e10d20bd3 100644 --- a/lib/Sema/CSStep.cpp +++ b/lib/Sema/CSStep.cpp @@ -272,7 +272,7 @@ StepResult ComponentStep::take(bool prevFailed) { } }); - auto *disjunction = CS.selectDisjunction(); + auto disjunction = CS.selectDisjunction(); auto *conjunction = CS.selectConjunction(); if (CS.isDebugMode()) { @@ -315,7 +315,8 @@ StepResult ComponentStep::take(bool prevFailed) { // Bindings usually happen first, but sometimes we want to prioritize a // disjunction or conjunction. if (bestBindings) { - if (disjunction && !bestBindings->favoredOverDisjunction(disjunction)) + if (disjunction && + !bestBindings->favoredOverDisjunction(disjunction->first)) return StepKind::Disjunction; if (conjunction && !bestBindings->favoredOverConjunction(conjunction)) @@ -338,9 +339,9 @@ StepResult ComponentStep::take(bool prevFailed) { return suspend( std::make_unique(*bestBindings, Solutions)); case StepKind::Disjunction: { - CS.retireConstraint(disjunction); + CS.retireConstraint(disjunction->first); return suspend( - std::make_unique(CS, disjunction, Solutions)); + std::make_unique(CS, *disjunction, Solutions)); } case StepKind::Conjunction: { CS.retireConstraint(conjunction); @@ -634,9 +635,30 @@ bool DisjunctionStep::shouldSkip(const DisjunctionChoice &choice) const { if (choice.isDisabled()) return skip("disabled"); - // Skip unavailable overloads (unless in diagnostic mode). - if (choice.isUnavailable() && !CS.shouldAttemptFixes()) - return skip("unavailable"); + if (!CS.shouldAttemptFixes()) { + // Skip unavailable overloads. + if (choice.isUnavailable()) + return skip("unavailable"); + + // Since the disfavored overloads are always located at the end of + // the partition they could be skipped if there was at least one + // valid solution for this partition already, because the solution + // they produce would always be worse. + if (choice.isDisfavored() && LastSolvedChoice) { + bool canSkipDisfavored = true; + auto &lastScore = LastSolvedChoice->second; + for (unsigned i = 0, n = unsigned(SK_DisfavoredOverload) + 1; i != n; + ++i) { + if (lastScore.Data[i] > 0) { + canSkipDisfavored = false; + break; + } + } + + if (canSkipDisfavored) + return skip("disfavored"); + } + } // If the solver already found a solution with a better overload choice that // can be unconditionally substituted by the current choice, skip the current diff --git a/lib/Sema/CSStep.h b/lib/Sema/CSStep.h index bfd103f5edccd..906fda9895050 100644 --- a/lib/Sema/CSStep.h +++ b/lib/Sema/CSStep.h @@ -613,9 +613,17 @@ class DisjunctionStep final : public BindingStep { 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; diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index 8b6ae459a75d4..6414950917ed0 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -204,6 +204,18 @@ bool TypeVariableType::Implementation::isCollectionLiteralType() const { locator->directlyAt()); } +bool TypeVariableType::Implementation::isNumberLiteralType() const { + return locator && locator->directlyAt(); +} + +bool TypeVariableType::Implementation::isFunctionResult() const { + return locator && locator->isLastElement(); +} + +bool TypeVariableType::Implementation::isTernary() const { + return locator && locator->directlyAt(); +} + void *operator new(size_t bytes, ConstraintSystem& cs, size_t alignment) { return cs.getAllocator().Allocate(bytes, alignment); @@ -454,6 +466,13 @@ TypeChecker::typeCheckTarget(SyntacticElementTarget &target, if (options.contains(TypeCheckExprFlags::DisableMacroExpansions)) csOptions |= ConstraintSystemFlags::DisableMacroExpansions; + if (Context.TypeCheckerOpts.EnableConstraintSolverPerformanceHacks || + evaluateOrDefault(Context.evaluator, + ModuleHasTypeCheckerPerformanceHacksEnabledRequest{ + dc->getParentModule()}, + false)) + csOptions |= ConstraintSystemFlags::EnablePerformanceHacks; + ConstraintSystem cs(dc, csOptions, diagnosticTransaction); if (auto *expr = target.getAsExpr()) { @@ -2448,3 +2467,11 @@ void ConstraintSystem::forEachExpr( expr->walk(ChildWalker(*this, callback)); } + +bool ModuleHasTypeCheckerPerformanceHacksEnabledRequest::evaluate( + Evaluator &evaluator, const ModuleDecl *module) const { + auto name = module->getRealName().str(); + return module->getASTContext().blockListConfig.hasBlockListAction( + name, BlockListKeyKind::ModuleName, + BlockListAction::ShouldUseTypeCheckerPerfHacks); +} \ No newline at end of file diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index a8425dcadf08d..4b6727a90916d 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -1672,7 +1672,7 @@ class ApplyClassifier { /// Check to see if the given function application throws, is async, or /// involves unsafe behavior. Classification classifyApply( - Expr *call, + ApplyExpr *call, const AbstractFunction &fnRef, Expr *calleeFn, const AnyFunctionType *fnType, @@ -1820,7 +1820,7 @@ class ApplyClassifier { // to fix their code. if (kind == EffectKind::Async && fnRef.getKind() == AbstractFunction::Function) { - if (auto *ctor = dyn_cast(calleeFn)) { + if (auto *ctor = dyn_cast(call->getFn())) { if (ctor->getFn()->isImplicit() && args->isUnlabeledUnary()) result.setDowngradeToWarning(true); } diff --git a/lib/Sema/TypeOfReference.cpp b/lib/Sema/TypeOfReference.cpp index e8911387832ab..313da3e045a03 100644 --- a/lib/Sema/TypeOfReference.cpp +++ b/lib/Sema/TypeOfReference.cpp @@ -1900,11 +1900,16 @@ Type ConstraintSystem::getEffectiveOverloadType(ConstraintLocator *locator, type, var, useDC, GetClosureType{*this}, ClosureIsolatedByPreconcurrency{*this}); } else if (isa(decl) || isa(decl)) { - if (decl->isInstanceMember() && - (!overload.getBaseType() || - (!overload.getBaseType()->getAnyNominal() && - !overload.getBaseType()->is()))) - return Type(); + if (decl->isInstanceMember()) { + auto baseTy = overload.getBaseType(); + if (!baseTy) + return Type(); + + baseTy = baseTy->getRValueType(); + if (!baseTy->getAnyNominal() && !baseTy->is() && + !baseTy->is()) + return Type(); + } // Cope with 'Self' returns. if (!decl->getDeclContext()->getSelfProtocolDecl()) { diff --git a/test/Constraints/async.swift b/test/Constraints/async.swift index 52f7a44e46d55..9985f169c0a22 100644 --- a/test/Constraints/async.swift +++ b/test/Constraints/async.swift @@ -212,10 +212,9 @@ func test_async_calls_in_async_context(v: Int) async { } // Only implicit `.init` should be accepted with a warning due type-checker previously picking an incorrect overload. - // FIXME: This should produce a warning once type-checker performance hacks are removed. - _ = Test(v) // Temporary okay + _ = Test(v) // expected-warning {{expression is 'async' but is not marked with 'await'; this is an error in the Swift 6 language mode}} expected-note {{call is 'async'}} _ = Test.init(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note {{call is 'async'}} Test.test(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note {{call is 'async'}} - Test(v).test(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note {{call is 'async'}} + Test(v).test(v) // expected-error {{expression is 'async' but is not marked with 'await'}} expected-note 2 {{call is 'async'}} } diff --git a/test/Constraints/bidirectional_conversions.swift b/test/Constraints/bidirectional_conversions.swift index de5077c507a32..43620893f9693 100644 --- a/test/Constraints/bidirectional_conversions.swift +++ b/test/Constraints/bidirectional_conversions.swift @@ -6,7 +6,7 @@ import CoreGraphics ///////////// -struct G { // expected-note {{arguments to generic parameter 'T' ('CGFloat?' and 'CGFloat') are expected to be equal}} +struct G { var t: T } @@ -32,8 +32,7 @@ func foo4(x: (() -> ())?, y: @escaping @convention(block) () -> ()) -> G<() -> ( func foo5(x: CGFloat?, y: Double) -> G { let g = G(t: x ?? y) - // FIXME - return g // expected-error {{cannot convert return expression of type 'G' to return type 'G'}} + return g } func foo6(x: Double?, y: CGFloat) -> G { diff --git a/test/Constraints/casts_swift6.swift b/test/Constraints/casts_swift6.swift index 5161a493e9dbd..17cbd89100741 100644 --- a/test/Constraints/casts_swift6.swift +++ b/test/Constraints/casts_swift6.swift @@ -25,9 +25,9 @@ func test_compatibility_coercions(_ arr: [Int], _ optArr: [Int]?, _ dict: [Strin // Make sure we error on the following in Swift 6 mode. _ = id(arr) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[Int]' vs. '[String]')}} - _ = (arr ?? []) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[String]' vs. '[Int]')}} - _ = (arr ?? [] ?? []) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[String]' vs. '[Int]')}} - // expected-error@-1{{conflicting arguments to generic parameter 'T' ('[String]' vs. '[Int]')}} + _ = (arr ?? []) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[Int]' vs. '[String]')}} + _ = (arr ?? [] ?? []) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[Int]' vs. '[String]')}} + // expected-error@-1{{conflicting arguments to generic parameter 'T' ('[Int]' vs. '[String]')}} _ = (optArr ?? []) as [String] // expected-error {{conflicting arguments to generic parameter 'T' ('[Int]' vs. '[String]'}} _ = (arr ?? []) as [String]? // expected-error {{'[Int]' is not convertible to '[String]?'}} diff --git a/test/Constraints/common_type.swift b/test/Constraints/common_type.swift index 32f3bf3c8bb35..bad4415cf63f1 100644 --- a/test/Constraints/common_type.swift +++ b/test/Constraints/common_type.swift @@ -1,6 +1,8 @@ // RUN: %target-typecheck-verify-swift -debug-constraints 2>%t.err // RUN: %FileCheck %s < %t.err +// REQUIRES: needs_adjustment_for_new_favoring + struct X { func g(_: Int) -> Int { return 0 } func g(_: Double) -> Int { return 0 } diff --git a/test/Constraints/implicit_double_cgfloat_conversion.swift b/test/Constraints/implicit_double_cgfloat_conversion.swift index 014b112f8de10..1915540fdd571 100644 --- a/test/Constraints/implicit_double_cgfloat_conversion.swift +++ b/test/Constraints/implicit_double_cgfloat_conversion.swift @@ -337,19 +337,6 @@ func test_implicit_conversion_clash_with_partial_application_check() { } } -// rdar://99352676 -// CHECK-LABEL: sil hidden [ossa] @$s34implicit_double_cgfloat_conversion20test_init_validationyyF : $@convention(thin) () -> () { -func test_init_validation() { - class Foo { - static let bar = 100.0 - - func getBar() -> CGFloat? { - return Self.bar - // CHECK: function_ref @$s12CoreGraphics7CGFloatVyACSdcfC : $@convention(method) (Double, @thin CGFloat.Type) -> CGFloat - } - } -} - func test_ternary_and_nil_coalescing() { func test(_: Double?) {} @@ -361,3 +348,59 @@ func test_ternary_and_nil_coalescing() { test(v ?? 0.0) // Ok } } + +do { + struct G { + init(_: T) {} + } + + func round(_: Double) -> Double {} + func round(_: T) -> T {} + + func test_cgfloat_over_double(withColors colors: Int, size: CGSize) -> G { + let g = G(1.0 / CGFloat(colors)) + return g // Ok + } + + func test_no_ambiguity(width: Int, height: Int) -> CGFloat { + let v = round(CGFloat(width / height) * 10) / 10.0 + return v // Ok + } +} + +func test_cgfloat_operator_is_attempted_with_literal_arguments(v: CGFloat?) { + // Make sure that @autoclosure thunk calls CGFloat./ and not Double./ + // CHECK-LABEL: sil private [transparent] [ossa] @$s34implicit_double_cgfloat_conversion05test_C45_operator_is_attempted_with_literal_arguments1vy12CoreGraphics7CGFloatVSg_tFAFyKXEfu_ + // CHECK: [[CGFLOAT_DIV_OP:%.*]] = function_ref @$s12CoreGraphics7CGFloatV34implicit_double_cgfloat_conversionE1doiyA2C_ACtFZ : $@convention(method) (CGFloat, CGFloat, @thin CGFloat.Type) -> CGFloat + // CHECK-NEXT: {{.*}} = apply [[CGFLOAT_DIV_OP]]({{.*}}, %2) : $@convention(method) (CGFloat, CGFloat, @thin CGFloat.Type) -> CGFloat + let ratio = v ?? (2.0 / 16.0) + let _: CGFloat = ratio // Ok +} + +// Make sure that optimizer doesn't favor CGFloat -> Double conversion +// in presence of CGFloat initializer, otherwise it could lead to ambiguities. +func test_explicit_cgfloat_use_avoids_ambiguity(v: Int) { + func test(_: CGFloat) -> CGFloat { 0 } + func test(_: Double) -> Double { 0 } + + func hasCGFloatElement(_: C) where C.Element == CGFloat {} + + let arr = [test(CGFloat(v))] + hasCGFloatElement(arr) // Ok + + var total = 0.0 // This is Double by default + total += test(CGFloat(v)) + CGFloat(v) // Ok +} + +// rdar://99352676 +// CHECK-LABEL: sil private [ossa] @$s34implicit_double_cgfloat_conversion20test_init_validationyyF3FooL_C6getBar12CoreGraphics7CGFloatVSgyF : $@convention(method) (@guaranteed Foo) -> Optional +func test_init_validation() { + class Foo { + static let bar = 100.0 + + func getBar() -> CGFloat? { + return Self.bar + // CHECK: function_ref @$s12CoreGraphics7CGFloatVyACSdcfC : $@convention(method) (Double, @thin CGFloat.Type) -> CGFloat + } + } +} diff --git a/test/Constraints/issue-52724.swift b/test/Constraints/issue-52724.swift index 3be619ad59310..acbdc0c453619 100644 --- a/test/Constraints/issue-52724.swift +++ b/test/Constraints/issue-52724.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -typecheck -verify -disable-constraint-solver-performance-hacks %s +// RUN: %target-swift-frontend -typecheck -verify %s // https://github.com/apple/swift/issues/52724 diff --git a/test/Constraints/nil-coalescing-favoring.swift b/test/Constraints/nil-coalescing-favoring.swift index 1723900d5a58d..5caa9ca548156 100644 --- a/test/Constraints/nil-coalescing-favoring.swift +++ b/test/Constraints/nil-coalescing-favoring.swift @@ -12,3 +12,53 @@ struct A { self.init(other ?? ._none) } } + +do { + class Super {} + class Sub: Super {} + + func flatMap(_: (Int) -> R?) -> R? {} + + func test() { + let dict: Dictionary + let sup: Super + + // CHECK: declref_expr type="(consuming Super?, @autoclosure () throws -> Super) throws -> Super" {{.*}} decl="Swift.(file).?? + let x = flatMap { dict[$0] } ?? sup // Ok + let _: Super = x + } +} + +// Reduced from vapor project. Favoring _only_ an overload of `??` and takes `T?` as a second parameter would result in an invalid solution. +extension Array where Element == UInt8 { + init?(decodingBase32 str: String) { + guard let decoded = str.utf8.withContiguousStorageIfAvailable({ Array(decodingBase32: $0) }) ?? Array(decodingBase32: Array(str.utf8)) else { // Ok + return nil + } + self = decoded + } + + init?(decodingBase32 bytes: C) where C: RandomAccessCollection, C.Element == UInt8, C.Index == Int { + fatalError() + } +} + +func test_no_incorrect_favoring(v: Int?, o: Int) { + func ternary(_: T, _: T) -> T { fatalError() } + + func nilCoelesing(_: T?, _: T) -> T { fatalError() } + func nilCoelesing(_: T?, _: T?) -> T? { fatalError() } + + let t1 = v ?? (true ? nil : v) + let t2 = v ?? ternary(nil, o) + + let s1 = nilCoelesing(v, (true ? nil : v)) + let s2 = nilCoelesing(v, ternary(nil, o)) + + func sameType(_: T, as: T.Type) {} + + sameType(t1, as: Int?.self) + sameType(t2, as: Int?.self) + sameType(s1, as: Int?.self) + sameType(s2, as: Int?.self) +} diff --git a/test/Constraints/old_hack_related_ambiguities.swift b/test/Constraints/old_hack_related_ambiguities.swift new file mode 100644 index 0000000000000..d94dc86cc3ae3 --- /dev/null +++ b/test/Constraints/old_hack_related_ambiguities.swift @@ -0,0 +1,385 @@ +// RUN: %target-typecheck-verify-swift + +func entity(_: Int) -> Int { + 0 +} + +struct Test { + func test(_ v: Int) -> Int { v } + func test(_ v: Int?) -> Int? { v } +} + +func test_ternary_literal(v: Test) -> Int? { + true ? v.test(0) : nil // Ok +} + +func test_ternary(v: Test) -> Int? { + // Because calls had favored types set if they were resolved during constraint generation. + true ? v.test(entity(0)) : nil // Ok +} + +do { + struct TestFloat { + func test(_ v: Float) -> Float { v } // expected-note {{found this candidate}} + func test(_ v: Float?) -> Float? { v } // expected-note {{found this candidate}} + } + + func test_ternary_non_default_literal(v: TestFloat) -> Float? { + true ? v.test(1.0) : nil // expected-error {{ambiguous use of 'test'}} + } +} + +do { + struct Test { + init(a: Int, b: Int = 0) throws {} + init?(a: Int?) {} + } + + func test(v: Int) -> Test? { + return Test(a: v) // Ok + } +} + +// error: initializer for conditional binding must have Optional type, not 'S' +do { + struct S { + let n: Int + + func test(v: String) -> Int { } + func test(v: String, flag: Bool = false) -> Int? { } + + + func verify(v: String) -> Int? { + guard let _ = test(v: v) else { // Ok + return nil + } + return 0 + } + } + + func f(_: String, _ p: Bool = false) -> S? { + nil + } + + func f(_ x: String) -> S { + fatalError() + } + + func g(_ x: String) -> Int? { + guard let y = f(x) else { + return nil + } + return y.n + } +} + +// ambiguities related to ~= +protocol _Error: Error {} + +extension _Error { + public static func ~=(lhs: Self, rhs: Self) -> Bool { + false + } + + public static func ~=(lhs: Error, rhs: Self) -> Bool { + false + } + + public static func ~=(lhs: Self, rhs: Error) -> Bool { + false + } +} + +enum CustomError { + case A +} + +extension CustomError: _Error {} + +func f(e: CustomError) { + if e ~= CustomError.A {} +} + +// Generic overload should be preferred over concrete one because the latter is non-default literal +struct Pattern {} + +func ~= (pattern: Pattern, value: String) -> Bool { + return false +} + +extension Pattern: ExpressibleByStringLiteral { + init(stringLiteral value: String) {} +} + +func test_default_tilda(v: String) { + _ = "hi" ~= v // Ok +} + +struct UUID {} + +struct LogKey { + init(base: some CustomStringConvertible, foo: Int = 0) { + } + + init(base: UUID, foo: Int = 0) { + } +} + +@available(swift 99) +extension LogKey { + init(base: String?) { + } + + init(base: UUID?) { + } +} + +func test_that_unavailable_init_is_not_used(x: String?) { + _ = LogKey(base: x ?? "??") +} + +// error: value of optional type 'UID?' must be unwrapped to a value of type 'UID' +struct S: Comparable { + static func <(lhs: Self, rhs: Self) -> Bool { + false + } +} + +func max(_ a: S?, _ b: S?) -> S? { + nil +} + +func test_stdlib_max_selection(s: S) -> S { + let new = max(s, s) + return new // Ok +} + +// error: initializer for conditional binding must have Optional type, not 'UnsafeMutablePointer' +do { + struct TestPointerConversions { + var p: UnsafeMutableRawPointer { get { fatalError() } } + + func f(_ p: UnsafeMutableRawPointer) { + // The old hack (which is now removed) couldn't handle member references, only direct declaration references. + guard let x = UnsafeMutablePointer(OpaquePointer(self.p)) else { + return + } + _ = x + + guard let x = UnsafeMutablePointer(OpaquePointer(p)) else { + // expected-error@-1 {{initializer for conditional binding must have Optional type, not 'UnsafeMutablePointer'}} + return + } + _ = x + } + } +} + +// error: initializer 'init(_:)' requires that 'T' conform to 'BinaryInteger' +do { + struct Config { + subscript(_ key: String) -> T? { nil } + subscript(_ key: String) -> Any? { nil } + } + + struct S { + init(maxQueueDepth: UInt) {} + } + + func f(config: Config) { + let maxQueueDepth = config["hi"] ?? 256 + _ = S(maxQueueDepth: UInt(maxQueueDepth)) + } +} + +// `tryOptimizeGenericDisjunction` is too aggressive sometimes, make sure that `` +// overload is _not_ selected in this case. +do { + func test(_ expression1: @autoclosure () throws -> T, accuracy: T) -> T {} + func test(_ expression1: @autoclosure () throws -> T, accuracy: T) -> T {} + + let result = test(10, accuracy: 1) + let _: Int = result +} + +// swift-distributed-tracing snippet that relies on old hack behavior. +protocol TracerInstant { +} + +extension Int: TracerInstant {} + +do { + enum SpanKind { + case `internal` + } + + func withSpan( + _ operationName: String, + at instant: @autoclosure () -> Instant, + context: @autoclosure () -> Int = 0, + ofKind kind: SpanKind = .internal + ) {} + + func withSpan( + _ operationName: String, + context: @autoclosure () -> Int = 0, + ofKind kind: SpanKind = .internal, + at instant: @autoclosure () -> some TracerInstant = 42 + ) {} + + withSpan("", at: 0) // Ok +} + +protocol ForAssert { + var isEmpty: Bool { get } +} + +extension ForAssert { + var isEmpty: Bool { false } +} + +do { + func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) {} + func assert(_ condition: Bool, _ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) {} + func assert(_ condition: Bool, file: StaticString = #fileID, line: UInt = #line) {} + + struct S : ForAssert { + var isEmpty: Bool { false } + } + + func test(s: S) { + assert(s.isEmpty, "") // Ok + } +} + +extension Double { + public static func * (left: Float, right: Double) -> Double { 0 } +} + +func test_non_default_literal_use(arg: Float) { + let v = arg * 2.0 // shouldn't use `(Float, Double) -> Double` overload + let _: Float = v // Ok +} + +// This should be ambiguous without contextual type but was accepted before during to +// unlabeled unary argument favoring. +func test_variadic_static_member_is_preferred_over_partially_applied_instance_overload() { + struct Test { + func fn() {} + static func fn(_: Test...) {} + } + + let t: Test + Test.fn(t) // Ok +} + +// Unary unlabeled argument favoring hacks never applied to subscripts + +protocol Subscriptable { +} + +extension Subscriptable { + subscript(key: String) -> Any? { nil } +} + +struct MyValue {} + +extension Dictionary : Subscriptable {} + +func test_that_unary_argument_hacks_do_not_apply_to_subscripts(dict: [String: MyValue]) { + let value = dict["hello"] + let _: MyValue? = value // Ok +} + +// Unlabeled unary argument hack was disabled if there were any protocol requirements +// or variadic generic overloads present in the result set (regadless of their viability). +// +// Remove the requirement and variadic overloads and this code would start failing even +// though it shouldn't! + +struct Future { +} + +protocol DB { + func get(_: Int, _: Int) -> Future +} + +extension DB { + func get(_: Int, _: Int = 42) async throws -> Int? { nil } + func get(_: Int) -> Future { .init() } + + func fetch(_: Int, _: Int = 42) async throws -> Int? { nil } + func fetch(_: Int) -> Future { .init() } + func fetch(values: repeat each T) -> Int { 42 } +} + +struct TestUnary { + var db: any DB + + func get(v: Int) async throws { + guard let _ = try await self.db.get(v) else { // Ok + return + } + + guard let _ = try await self.db.fetch(v) else { // Ok + return + } + } +} + +// Prevent non-optional overload of `??` to be favored when all initializers are failable. + +class A {} +class B {} + +protocol P { + init() +} + +extension P { + init?(v: A) { self.init() } +} + +struct V : P { + init() {} + + @_disfavoredOverload + init?(v: B?) {} + + // Important to keep this to make sure that disabled constraints + // are handled properly. + init(other: T) where T.Element == Character {} +} + +class TestFailableOnly { + var v: V? + + func test(defaultB: B) { + guard let _ = self.v ?? V(v: defaultB) else { // OK (no warnings) + return + } + } +} + +do { + @_disfavoredOverload + func test(over: Int, that: String = "", block: @escaping (Int) throws -> Void) async throws {} + func test(over: Int, that: String = "", block: @escaping (Int) throws -> Void) throws {} // expected-note {{found this candidate}} + func test(over: Int, other: String = "", block: @escaping (Int) throws -> Void) throws {} // expected-note {{found this candidate}} + + func performLocal(v: Int, block: @escaping (Int) throws -> Void) async throws { + try await test(over: v, block: block) // expected-error {{ambiguous use of 'test'}} + } + + // The hack applied only to `OverloadedDeclRefExpr`s. + struct MemberTest { + @_disfavoredOverload + func test(over: Int, that: String = "", block: @escaping (Int) throws -> Void) async throws {} + func test(over: Int, that: String = "", block: @escaping (Int) throws -> Void) throws {} + func test(over: Int, other: String = "", block: @escaping (Int) throws -> Void) throws {} + + func performMember(v: Int, block: @escaping (Int) throws -> Void) async throws { + try await test(over: v, block: block) // Ok + } + } +} diff --git a/test/Constraints/operator.swift b/test/Constraints/operator.swift index a54ff50a4770d..d199f88c65357 100644 --- a/test/Constraints/operator.swift +++ b/test/Constraints/operator.swift @@ -330,3 +330,66 @@ enum I60954 { } init?(_ string: S) where S: StringProtocol {} // expected-note{{where 'S' = 'I60954'}} } + +infix operator <<<>>> : DefaultPrecedence + +protocol P5 { +} + +struct Expr : P6 {} + +protocol P6: P5 { +} + +extension P6 { + public static func <<<>>> (lhs: Self, rhs: (any P5)?) -> Expr { Expr() } + public static func <<<>>> (lhs: (any P5)?, rhs: Self) -> Expr { Expr() } + public static func <<<>>> (lhs: Self, rhs: some P6) -> Expr { Expr() } + + public static prefix func ! (value: Self) -> Expr { + Expr() + } +} + +extension P6 { + public static func != (lhs: Self, rhs: some P6) -> Expr { + !(lhs <<<>>> rhs) // Ok + } +} + +do { + struct Value : P6 { + } + + struct Column: P6 { + } + + func test(col: Column, val: Value) -> Expr { + col <<<>>> val // Ok + } + + func test(col: Column, val: some P6) -> Expr { + col <<<>>> val // Ok + } + + func test(col: some P6, val: Value) -> Expr { + col <<<>>> val // Ok + } +} + +// Make sure that ?? selects an overload that doesn't produce an optional. +do { + class Obj { + var x: String! + } + + class Child : Obj { + func x() -> String? { nil } + static func x(_: Int) -> String { "" } + } + + func test(arr: [Child], v: String, defaultV: Child) -> Child { + let result = arr.first { $0.x == v } ?? defaultV + return result // Ok + } +} diff --git a/test/Constraints/overload.swift b/test/Constraints/overload.swift index 87c132e21c5ac..699c1c06d97cc 100644 --- a/test/Constraints/overload.swift +++ b/test/Constraints/overload.swift @@ -350,6 +350,28 @@ do { } } +// Make sure that the solver properly handles mix of non-default integer and floating-point literals +do { + func test( + withInitialValue initialValue: Float, + increment: Float, + count: Int) -> [Float] {} + + func test( + withInitialValue initialValue: Double, + increment: Double, + count: Int) -> [Double] {} + + + func testDoubleVsFloat(count: Int) { + let returnedResult = test(withInitialValue: 0, + increment: 0.1, + count: count) + + let _: [Double] = returnedResult // Ok + } +} + do { struct S { let x: Int diff --git a/test/Constraints/perf_hacks_with_block_list.swift b/test/Constraints/perf_hacks_with_block_list.swift new file mode 100644 index 0000000000000..5a965e28c1625 --- /dev/null +++ b/test/Constraints/perf_hacks_with_block_list.swift @@ -0,0 +1,16 @@ +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +// RUN: %target-swift-frontend -typecheck -module-name Test -debug-constraints -blocklist-file %t/blocklist.yaml -verify %t/main.swift 2>%t.err +// RUN: %FileCheck %t/main.swift < %t.err + +//--- blocklist.yaml +--- +ShouldUseTypeCheckerPerfHacks: + ModuleName: + - Test + +//--- main.swift +_ = 1 + 2 + 3 + +// CHECK: [favored] {{.*}} bound to decl Swift.(file).Int extension.+ : (Int.Type) -> (Int, Int) -> Int diff --git a/test/Constraints/warn_long_compile.swift b/test/Constraints/warn_long_compile.swift index fc425058bed31..cc28934a0e155 100644 --- a/test/Constraints/warn_long_compile.swift +++ b/test/Constraints/warn_long_compile.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -warn-long-expression-type-checking=1 -disable-constraint-solver-performance-hacks -warn-long-function-bodies=1 +// RUN: %target-typecheck-verify-swift -warn-long-expression-type-checking=1 -warn-long-function-bodies=1 @_silgen_name("generic_foo") func foo(_ x: T) -> T diff --git a/test/IDE/complete_operators.swift b/test/IDE/complete_operators.swift index 2772678e1dbc2..f7e0392a2b5a6 100644 --- a/test/IDE/complete_operators.swift +++ b/test/IDE/complete_operators.swift @@ -52,7 +52,7 @@ func testPostfix6() { func testPostfix7() { 1 + 2 * 3.0#^POSTFIX_7^# } -// POSTFIX_7: Decl[PostfixOperatorFunction]/CurrModule: ***[#Double#] +// POSTFIX_7: Decl[PostfixOperatorFunction]/CurrModule/TypeRelation[Convertible]: ***[#Double#] func testPostfix8(x: S) { x#^POSTFIX_8^# diff --git a/test/SILOptimizer/infinite_recursion.swift b/test/SILOptimizer/infinite_recursion.swift index 42e64ce15ff2f..c794ba76d24e6 100644 --- a/test/SILOptimizer/infinite_recursion.swift +++ b/test/SILOptimizer/infinite_recursion.swift @@ -282,7 +282,7 @@ public class U { } func == (l: S?, r: S?) -> Bool { - if l == nil && r == nil { return true } // expected-warning {{function call causes an infinite recursion}} + if l == nil && r == nil { return true } guard let l = l, let r = r else { return false } return l === r } diff --git a/test/expr/expressions.swift b/test/expr/expressions.swift index b30d808a56264..d84994169fbb4 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 (~Copyable & ~Escapable).Type' to 'nil' always returns false}} -_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'nil' always returns false}} -_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'nil' always returns true}} -_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'nil' always returns true}} +_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns false}} +_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns false}} +_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns true}} +_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns true}} _ = Int.self == .none // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'Optional.none' always returns false}} _ = .none == Int.self // expected-warning {{comparing non-optional value of type 'any (~Copyable & ~Escapable).Type' to 'Optional.none' always returns false}} diff --git a/validation-test/Sema/SwiftUI/swiftui_multiple_chained_members_in_inner_closure.swift b/validation-test/Sema/SwiftUI/swiftui_multiple_chained_members_in_inner_closure.swift new file mode 100644 index 0000000000000..dbfc309569c85 --- /dev/null +++ b/validation-test/Sema/SwiftUI/swiftui_multiple_chained_members_in_inner_closure.swift @@ -0,0 +1,34 @@ +// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx12 -solver-scope-threshold=10000 + +// REQUIRES: OS=macosx +// REQUIRES: objc_interop + +import Foundation +import SwiftUI + +struct MyView: View { + public enum Style { + case focusRing(platterSize: CGSize, stroke: CGFloat, offset: CGFloat) + } + + var style: Style + var isFocused: Bool + var focusColor: Color + + var body: some View { + Group { + switch style { + case let .focusRing(platterSize: platterSize, stroke: focusRingStroke, offset: focusRingOffset): + Circle() + .overlay { + Circle() + .stroke(isFocused ? focusColor : Color.clear, lineWidth: focusRingStroke) + .frame( + width: platterSize.width + (2 * focusRingOffset) + focusRingStroke, + height: platterSize.height + (2 * focusRingOffset) + focusRingStroke + ) + } + } + } + } +} diff --git a/validation-test/Sema/custom_array_literal_with_operators.swift b/validation-test/Sema/custom_array_literal_with_operators.swift new file mode 100644 index 0000000000000..dd064743356bc --- /dev/null +++ b/validation-test/Sema/custom_array_literal_with_operators.swift @@ -0,0 +1,39 @@ +// RUN: %target-typecheck-verify-swift + +protocol ScalarOrArray { +} + +protocol SpecialValue: ScalarOrArray { +} + +extension Array: ScalarOrArray where Element: SpecialValue { +} + +struct CustomArray : ScalarOrArray { +} + +extension CustomArray : ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Int32...) { + self.init() + } +} + +extension Int32: SpecialValue { +} + +func +(_: T, _: CustomArray) -> CustomArray {} +func +(_: CustomArray, _: T) -> CustomArray {} +func +(_: CustomArray, _: CustomArray) -> CustomArray {} + +extension Sequence where Element == Int { + var asInt32: [Int32] { [] } +} + +extension Array where Element == Int { + var asInt32: [Int32] { [] } +} + +func test(v: Int32, b: [Int]) -> [Int32] { + let result = [1, v - v] + b.asInt32 + return result // Ok +} diff --git a/validation-test/Sema/implicit_cgfloat_double_conversion_correctness.swift b/validation-test/Sema/implicit_cgfloat_double_conversion_correctness.swift index 121812a279c67..4aeb205e7f129 100644 --- a/validation-test/Sema/implicit_cgfloat_double_conversion_correctness.swift +++ b/validation-test/Sema/implicit_cgfloat_double_conversion_correctness.swift @@ -47,3 +47,8 @@ func test_atan_ambiguity(points: (CGPoint, CGPoint)) { test = atan((points.1.y - points.0.y) / (points.1.x - points.0.x)) // Ok _ = test } + +func test_ambigity_with_generic_funcs(a: CGFloat, b: CGFloat) -> [CGFloat] { + let result = [round(abs(a - b) * 100) / 100.0] + return result +} diff --git a/validation-test/Sema/type_checker_perf/fast/array_concatenation.swift b/validation-test/Sema/type_checker_perf/fast/array_concatenation.swift index 9ba40cc773b83..873889b4a20bd 100644 --- a/validation-test/Sema/type_checker_perf/fast/array_concatenation.swift +++ b/validation-test/Sema/type_checker_perf/fast/array_concatenation.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -disable-constraint-solver-performance-hacks +// RUN: %target-typecheck-verify-swift // Self-contained test case protocol P1 {}; func f(_: T, _: T) -> T { fatalError() } diff --git a/validation-test/Sema/type_checker_perf/fast/array_literal_with_operators_and_double_literals.swift b/validation-test/Sema/type_checker_perf/fast/array_literal_with_operators_and_double_literals.swift new file mode 100644 index 0000000000000..49ce8b4d6abac --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/array_literal_with_operators_and_double_literals.swift @@ -0,0 +1,31 @@ +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) %s -typecheck -solver-expression-time-threshold=1 + +// REQUIRES: asserts,no_asan +// REQUIRES: objc_interop + +// FIXME: This should be a scale-test but it doesn't allow passing `%clang-importer-sdk` + +// This import is important because it brings CGFloat and +// enables Double<->CGFloat implicit conversion that affects +// literals below. +import Foundation + +let p/*: [(String, Bool, Bool, Double)]*/ = [ + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0), + ("", true, true, 0 * 0.0 * 0.0) +] diff --git a/validation-test/Sema/type_checker_perf/fast/array_of_literals_and_operators_cast_to_float.swift.gyb b/validation-test/Sema/type_checker_perf/fast/array_of_literals_and_operators_cast_to_float.swift.gyb new file mode 100644 index 0000000000000..2cd48795d50c5 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/array_of_literals_and_operators_cast_to_float.swift.gyb @@ -0,0 +1,10 @@ +// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s +// REQUIRES: asserts,no_asan + +let _ = [ + 0, +%for i in range(2, N+2): + 1/${i}, +%end + 1 +] as [Float] diff --git a/validation-test/Sema/type_checker_perf/fast/builder_with_nested_closures.swift b/validation-test/Sema/type_checker_perf/fast/builder_with_nested_closures.swift new file mode 100644 index 0000000000000..e4c1ebb55efd4 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/builder_with_nested_closures.swift @@ -0,0 +1,71 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=200 +// REQUIRES: tools-release,no_asan +// REQUIRES: OS=macosx + +struct Time { + static func +(_: Time, _: Double) -> Time { + Time() + } + + static func now() -> Time { Time() } +} + +struct Queue { + static let current = Queue() + + func after(deadline: Time, execute: () -> Void) {} + func after(deadline: Time, execute: (Int) -> Void) {} +} + +func compute(_: () -> Void) {} + +protocol P { +} + +@resultBuilder +struct Builder { + static func buildExpression(_ v: T) -> T { + v + } + + static func buildBlock(_ v: T) -> T { + v + } + + static func buildBlokc(_ v0: T0, _ v1: T1) -> (T0, T1) { + (v0, v1) + } +} + +struct MyP: P { + func onAction(_: () -> Void) -> some P { self } +} + +class Test { + var value = 0.0 + + @Builder func test() -> some P { + MyP().onAction { + Queue.current.after(deadline: .now() + 0.1) { + compute { + value = 0.3 + Queue.current.after(deadline: .now() + 0.2) { + compute { + value = 1.0 + Queue.current.after(deadline: .now() + 0.2) { + compute { + value = 0.3 + Queue.current.after(deadline: .now() + 0.2) { + compute { + value = 1.0 + } + } + } + } + } + } + } + } + } + } +} diff --git a/validation-test/Sema/type_checker_perf/fast/cgfloat_with_unary_operators.swift b/validation-test/Sema/type_checker_perf/fast/cgfloat_with_unary_operators.swift new file mode 100644 index 0000000000000..ebf7490e9cfdf --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/cgfloat_with_unary_operators.swift @@ -0,0 +1,19 @@ +// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 + +// REQUIRES: OS=macosx,no_asan +// REQUIRES: objc_interop + +import Foundation +import CoreGraphics +import simd + +func test( + a: CGFloat, + b: CGFloat +) -> CGFloat { + exp(-a * b) * + (a * -sin(a * b) * a + ((a * b + a) / b) * cos(a * b) * a) + + exp(-a * b) * + (-b) * + (a * cos(a * b) + ((a * b + a) / b) * sin(a * b)) +} diff --git a/validation-test/Sema/type_checker_perf/fast/collection_subscript_chains.swift.gyb b/validation-test/Sema/type_checker_perf/fast/collection_subscript_chains.swift.gyb new file mode 100644 index 0000000000000..b6c74599d5707 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/collection_subscript_chains.swift.gyb @@ -0,0 +1,13 @@ +// RUN: %scale-test --begin 1 --end 15 --step 1 --select NumLeafScopes %s --expected-exit-code 0 +// REQUIRES: asserts,no_asan + +func test(carrierDict: [String : Double]) { + var exhaustTemperature: Double + exhaustTemperature = ( + (carrierDict[""] ?? 0.0) + +%for i in range(N): + (carrierDict[""] ?? 0.0) + +%end + (carrierDict[""] ?? 0.0) + ) / 4 +} diff --git a/validation-test/Sema/type_checker_perf/fast/complex_swiftui_padding_conditions.swift b/validation-test/Sema/type_checker_perf/fast/complex_swiftui_padding_conditions.swift new file mode 100644 index 0000000000000..c9f3af5130530 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/complex_swiftui_padding_conditions.swift @@ -0,0 +1,17 @@ +// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx10.15 -solver-scope-threshold=5500 +// REQUIRES: OS=macosx + +import SwiftUI + +func test(a: [(offset: Int, element: Double)], + c: Color, + x: CGFloat, + n: Int +) -> some View { + ForEach(a, id: \.offset) { i, r in + RoundedRectangle(cornerRadius: r, style: .continuous) + .stroke(c, lineWidth: 1) + .padding(.horizontal, x / Double(n) * Double(n - 1 - i) / 2) + .padding(.vertical, x / Double(n) * Double(n - 1 - i) / 2) + } +} diff --git a/validation-test/Sema/type_checker_perf/fast/contextual_cgfloat_type_with_operator_chain_arguments.swift b/validation-test/Sema/type_checker_perf/fast/contextual_cgfloat_type_with_operator_chain_arguments.swift new file mode 100644 index 0000000000000..72fb47673c4ea --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/contextual_cgfloat_type_with_operator_chain_arguments.swift @@ -0,0 +1,17 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=50 + +// REQUIRES: OS=macosx,no_asan +// REQUIRES: objc_interop + +import Foundation + +struct Size { + var width: CGFloat + var height: CGFloat +} + +func frame(width: CGFloat?, height: CGFloat?) {} + +func test(size: Size?) { + frame(width: ((size?.width ?? 0) * 1) + 1.0, height: ((size?.height ?? 0) * 1) + 1.0) +} diff --git a/validation-test/Sema/type_checker_perf/fast/member_chains_and_operators_with_iou_base_types.swift b/validation-test/Sema/type_checker_perf/fast/member_chains_and_operators_with_iou_base_types.swift new file mode 100644 index 0000000000000..d00b81af40f38 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/member_chains_and_operators_with_iou_base_types.swift @@ -0,0 +1,35 @@ +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) %s -typecheck -solver-expression-time-threshold=1 + +// REQUIRES: OS=macosx,no_asan +// REQUIRES: objc_interop + +import Foundation + +struct CGRect { + var x: CGFloat + + init(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) { } + init(x: Double, y: Double, width: Double, height: Double) { } +} + +protocol View {} + +extension Optional: View where Wrapped: View {} + +extension View { + func frame() -> some View { self } + func frame(x: Int, y: Int, w: Int, z: Int) -> some View { self } + func frame(y: Bool) -> some View { self } +} + +struct NSView { + var frame: CGRect +} + +func test(margin: CGFloat, view: NSView!) -> CGRect { + // `view` is first attempted as `NSView?` and only if that fails is force unwrapped + return CGRect(x: view.frame.x + margin, + y: view.frame.x + margin, + width: view.frame.x - view.frame.x - view.frame.x - (margin * 2), + height: margin) +} diff --git a/validation-test/Sema/type_checker_perf/slow/nil_coalescing.swift.gyb b/validation-test/Sema/type_checker_perf/fast/nil_coalescing.swift.gyb similarity index 58% rename from validation-test/Sema/type_checker_perf/slow/nil_coalescing.swift.gyb rename to validation-test/Sema/type_checker_perf/fast/nil_coalescing.swift.gyb index 2ef5c8f16a3d7..af8639d9bc159 100644 --- a/validation-test/Sema/type_checker_perf/slow/nil_coalescing.swift.gyb +++ b/validation-test/Sema/type_checker_perf/fast/nil_coalescing.swift.gyb @@ -1,4 +1,4 @@ -// RUN: %scale-test --invert-result --begin 1 --end 5 --step 1 --select NumLeafScopes %s +// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s // REQUIRES: asserts,no_asan func t(_ x: Int?) -> Int { diff --git a/validation-test/Sema/type_checker_perf/fast/nil_coalescing_dictionary_values.swift.gyb b/validation-test/Sema/type_checker_perf/fast/nil_coalescing_dictionary_values.swift.gyb new file mode 100644 index 0000000000000..125e6b4218266 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/nil_coalescing_dictionary_values.swift.gyb @@ -0,0 +1,10 @@ +// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s -Xfrontend=-typecheck +// REQUIRES: asserts, no_asan + +let x: Int? + +let _ = [ +%for i in range(0, N): + "${i}" : x ?? 0, +%end +] diff --git a/validation-test/Sema/type_checker_perf/fast/operator_chain_with_closures.swift.gyb b/validation-test/Sema/type_checker_perf/fast/operator_chain_with_closures.swift.gyb new file mode 100644 index 0000000000000..2703b185b23f1 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/operator_chain_with_closures.swift.gyb @@ -0,0 +1,15 @@ +// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s +// REQUIRES: asserts,no_asan + +struct Test { + var values: [Int] +} + +func test(t: [Test]) { + let _ = 0 + + 1 +%for i in range(1, N): + + 1 +%end + + t.map(\.values.count).reduce(0, +) +} diff --git a/validation-test/Sema/type_checker_perf/slow/operator_chain_with_hetergeneous_arguments.swift b/validation-test/Sema/type_checker_perf/fast/operator_chain_with_hetergeneous_arguments.swift similarity index 90% rename from validation-test/Sema/type_checker_perf/slow/operator_chain_with_hetergeneous_arguments.swift rename to validation-test/Sema/type_checker_perf/fast/operator_chain_with_hetergeneous_arguments.swift index 5d6c8c4440ece..787509fb565b2 100644 --- a/validation-test/Sema/type_checker_perf/slow/operator_chain_with_hetergeneous_arguments.swift +++ b/validation-test/Sema/type_checker_perf/fast/operator_chain_with_hetergeneous_arguments.swift @@ -5,5 +5,4 @@ func test(bytes: Int, length: UInt32) { // left-hand side of `>=` is `Int` and right-hand side is a chain of `UInt32` inferred from `length` _ = bytes >= 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + length - // expected-error@-1 {{reasonable time}} } diff --git a/validation-test/Sema/type_checker_perf/fast/operator_chains_separated_by_ternary.swift.gyb b/validation-test/Sema/type_checker_perf/fast/operator_chains_separated_by_ternary.swift.gyb new file mode 100644 index 0000000000000..9e3a298c66295 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/operator_chains_separated_by_ternary.swift.gyb @@ -0,0 +1,18 @@ +// RUN: %scale-test --begin 1 --end 12 --step 1 --select NumLeafScopes %s +// REQUIRES: asserts,no_asan + +func compute(_: UInt32) { +} + +func test(cond: Bool) { + compute(cond + ? 1 +%for i in range(1, N): + + 1 +%end + : 1 +%for i in range(1, N): + * 1 +%end + ) +} diff --git a/validation-test/Sema/type_checker_perf/fast/operators_inside_closure.swift b/validation-test/Sema/type_checker_perf/fast/operators_inside_closure.swift new file mode 100644 index 0000000000000..49d44feb86b28 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/operators_inside_closure.swift @@ -0,0 +1,9 @@ +// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=10000 +// REQUIRES: tools-release,no_asan + +// Selecting operators from the closure before arguments to `zip` makes this "too complex" +func compute(_ ids: [UInt64]) { + let _ = zip(ids[ids.indices.dropLast()], ids[ids.indices.dropFirst()]).map { pair in + ((pair.0 % 2 == 0) && (pair.1 % 2 == 1)) ? UInt64(pair.1 - pair.0) : 42 + } +} diff --git a/validation-test/Sema/type_checker_perf/fast/rdar133340307.swift b/validation-test/Sema/type_checker_perf/fast/rdar133340307.swift new file mode 100644 index 0000000000000..9740b4d15f06c --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/rdar133340307.swift @@ -0,0 +1,134 @@ +// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 +// REQUIRES: tools-release,no_asan + +public protocol Applicative {} + +public struct Kind {} + +public extension Applicative { + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } + + static func product (_ fa:Kind, _ fb:Kind) -> Kind { + fatalError() + } +} + +public extension Applicative { + static func zip (_ fa0:Kind, _ fa1:Kind, _ fa2:Kind, _ fa3:Kind, _ fa4:Kind, _ fa5:Kind, _ fa6:Kind, _ fa7:Kind, _ fa8:Kind, _ fa9:Kind, _ fa10:Kind, _ fa11:Kind, _ fa12:Kind, _ fa13:Kind, _ fa14:Kind, _ fa15:Kind, _ fa16:Kind, _ fa17:Kind, _ fa18:Kind, _ fa19:Kind, _ fa20:Kind, _ fa21:Kind, _ fa22:Kind, _ fa23:Kind, _ fa24:Kind, _ fa25:Kind, _ fa26:Kind, _ fa27:Kind, _ fa28:Kind, _ fa29:Kind) -> Kind { + product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(product(fa0, fa1), fa2), fa3), fa4), fa5), fa6), fa7), fa8), fa9), fa10), fa11), fa12), fa13), fa14), fa15), fa16), fa17), fa18), fa19), fa20), fa21), fa22), fa23), fa24), fa25), fa26), fa27), fa28), fa29) // Ok + } +} diff --git a/validation-test/Sema/type_checker_perf/fast/rdar144100160.swift b/validation-test/Sema/type_checker_perf/fast/rdar144100160.swift new file mode 100644 index 0000000000000..7e80fd5c3d6c8 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/rdar144100160.swift @@ -0,0 +1,12 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=100 +// REQUIRES: asserts,no_asan + +typealias TimeInterval = Double + +struct Date { + func addingTimeInterval(_: TimeInterval) -> Date { Date() } +} + +func test(date: Date) { + _ = date.addingTimeInterval(TimeInterval(60 * 60 * 24 * 6 + 12 * 60 + 12 + 1)) +} diff --git a/validation-test/Sema/type_checker_perf/slow/rdar17170728.swift b/validation-test/Sema/type_checker_perf/fast/rdar17170728.swift similarity index 74% rename from validation-test/Sema/type_checker_perf/slow/rdar17170728.swift rename to validation-test/Sema/type_checker_perf/fast/rdar17170728.swift index 62c2bb2ea367b..1e64e4194e4b6 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar17170728.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar17170728.swift @@ -5,7 +5,6 @@ let i: Int? = 1 let j: Int? let k: Int? = 2 -// expected-error@+1 {{the compiler is unable to type-check this expression in reasonable time}} let _ = [i, j, k].reduce(0 as Int?) { $0 != nil && $1 != nil ? $0! + $1! : ($0 != nil ? $0! : ($1 != nil ? $1! : nil)) } diff --git a/validation-test/Sema/type_checker_perf/slow/rdar22770433.swift b/validation-test/Sema/type_checker_perf/fast/rdar22770433.swift similarity index 74% rename from validation-test/Sema/type_checker_perf/slow/rdar22770433.swift rename to validation-test/Sema/type_checker_perf/fast/rdar22770433.swift index f063e685689de..0c51c9346d7ac 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar22770433.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar22770433.swift @@ -2,7 +2,6 @@ // REQUIRES: tools-release,no_asan func test(n: Int) -> Int { - // expected-error@+1 {{the compiler is unable to type-check this expression in reasonable time}} return n == 0 ? 0 : (0.. 0 && $1 % 2 == 0) ? ((($0 + $1) - ($0 + $1)) / ($1 - $0)) + (($0 + $1) / ($1 - $0)) : $0 } diff --git a/validation-test/Sema/type_checker_perf/fast/rdar31742586.swift b/validation-test/Sema/type_checker_perf/fast/rdar31742586.swift new file mode 100644 index 0000000000000..83a3aa1ffa110 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/rdar31742586.swift @@ -0,0 +1,6 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=1000 +// REQUIRES: tools-release,no_asan + +func rdar31742586() -> Double { + return -(1 + 2) + -(3 + 4) + 5 - (-(1 + 2) + -(3 + 4) + 5) +} diff --git a/validation-test/Sema/type_checker_perf/slow/rdar35213699.swift b/validation-test/Sema/type_checker_perf/fast/rdar35213699.swift similarity index 67% rename from validation-test/Sema/type_checker_perf/slow/rdar35213699.swift rename to validation-test/Sema/type_checker_perf/fast/rdar35213699.swift index 5d89ab1541a89..b5a73088942bb 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar35213699.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar35213699.swift @@ -3,6 +3,5 @@ func test() { let _: UInt = 1 * 2 + 3 * 4 + 5 * 6 + 7 * 8 + 9 * 10 + 11 * 12 + 13 * 14 - // expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time}} } diff --git a/validation-test/Sema/type_checker_perf/slow/rdar46713933_literal_arg.swift b/validation-test/Sema/type_checker_perf/fast/rdar46713933_literal_arg.swift similarity index 85% rename from validation-test/Sema/type_checker_perf/slow/rdar46713933_literal_arg.swift rename to validation-test/Sema/type_checker_perf/fast/rdar46713933_literal_arg.swift index a0628335b9c36..5256a92a787c7 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar46713933_literal_arg.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar46713933_literal_arg.swift @@ -8,5 +8,4 @@ func wrap(_ key: String, _ value: T) -> T { retur func wrapped() -> Int { return wrap("1", 1) + wrap("1", 1) + wrap("1", 1) + wrap("1", 1) + wrap("1", 1) + wrap("1", 1) - // expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time}} } diff --git a/validation-test/Sema/type_checker_perf/fast/rdar47492691.swift b/validation-test/Sema/type_checker_perf/fast/rdar47492691.swift index 47fcdcb0771af..7a25cca734f79 100644 --- a/validation-test/Sema/type_checker_perf/fast/rdar47492691.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar47492691.swift @@ -7,3 +7,7 @@ import simd func test(foo: CGFloat, bar: CGFloat) { _ = CGRect(x: 0.0 + 1.0, y: 0.0 + foo, width: 3.0 - 1 - 1 - 1.0, height: bar) } + +func test_with_generic_func_and_literals(bounds: CGRect) { + _ = CGRect(x: 0, y: 0, width: 1, height: bounds.height - 2 + bounds.height / 2 + max(bounds.height / 2, bounds.height / 2)) +} diff --git a/validation-test/Sema/type_checker_perf/slow/rdar91310777.swift b/validation-test/Sema/type_checker_perf/fast/rdar91310777.swift similarity index 66% rename from validation-test/Sema/type_checker_perf/slow/rdar91310777.swift rename to validation-test/Sema/type_checker_perf/fast/rdar91310777.swift index eb9dcbee848c8..18450b15fe401 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar91310777.swift +++ b/validation-test/Sema/type_checker_perf/fast/rdar91310777.swift @@ -9,7 +9,6 @@ func test() { compute { print(x) let v: UInt64 = UInt64((24 / UInt32(1)) + UInt32(0) - UInt32(0) - 24 / 42 - 42) - // expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions}} print(v) } } diff --git a/validation-test/Sema/type_checker_perf/fast/should_skip_bitwise_2.swift b/validation-test/Sema/type_checker_perf/fast/should_skip_bitwise_2.swift index fdcfab315d3ad..d7d37c8b65b93 100644 --- a/validation-test/Sema/type_checker_perf/fast/should_skip_bitwise_2.swift +++ b/validation-test/Sema/type_checker_perf/fast/should_skip_bitwise_2.swift @@ -1,3 +1,3 @@ -// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=2000 +// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=4000 func f896() { let _ = ((0 >> ((0 >> 0) + ((0 / 0) & 0))) >> (0 << ((0 << 0) >> (0 << (0 << 0))))) } diff --git a/validation-test/Sema/type_checker_perf/slow/simd_scalar_multiple.swift.gyb b/validation-test/Sema/type_checker_perf/fast/simd_scalar_multiple.swift.gyb similarity index 70% rename from validation-test/Sema/type_checker_perf/slow/simd_scalar_multiple.swift.gyb rename to validation-test/Sema/type_checker_perf/fast/simd_scalar_multiple.swift.gyb index 497afd10785f5..1d5e4f9e0ea6f 100644 --- a/validation-test/Sema/type_checker_perf/slow/simd_scalar_multiple.swift.gyb +++ b/validation-test/Sema/type_checker_perf/fast/simd_scalar_multiple.swift.gyb @@ -1,4 +1,4 @@ -// RUN: %scale-test --invert-result --begin 1 --end 5 --step 1 --select NumLeafScopes %s +// RUN: %scale-test --begin 1 --end 5 --step 1 --select NumLeafScopes %s // REQUIRES: asserts,no_asan diff --git a/validation-test/Sema/type_checker_perf/fast/string_interpolations.swift.gyb b/validation-test/Sema/type_checker_perf/fast/string_interpolations.swift.gyb new file mode 100644 index 0000000000000..fd54e39b609c7 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/string_interpolations.swift.gyb @@ -0,0 +1,26 @@ +// RUN: %scale-test --begin 1 --end 10 --step 1 --select NumLeafScopes %s -Xfrontend=-typecheck +// REQUIRES: asserts,no_asan + +struct MyString { +} + +extension MyString: ExpressibleByStringLiteral { + init(stringLiteral value: String) {} +} + +func +(_: MyString, _: MyString) -> MyString {} + +func test(_: @autoclosure () -> String) {} +func test(_: () -> String) {} +func test(_: Character) {} + +func test(names: [String]) { + var resultString = "" + for name in names { + test("\(name)" +%for i in range(0, N): + + "\(name)" +%end + + "\(name)") + } +} diff --git a/validation-test/Sema/type_checker_perf/slow/swift_package_index_1.swift b/validation-test/Sema/type_checker_perf/fast/swift_package_index_1.swift similarity index 77% rename from validation-test/Sema/type_checker_perf/slow/swift_package_index_1.swift rename to validation-test/Sema/type_checker_perf/fast/swift_package_index_1.swift index 934a80472f9c9..edcc58b5c71eb 100644 --- a/validation-test/Sema/type_checker_perf/slow/swift_package_index_1.swift +++ b/validation-test/Sema/type_checker_perf/fast/swift_package_index_1.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-scope-threshold=1000 +// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=700 // REQUIRES: tools-release,no_asan public class Cookie { @@ -13,7 +13,7 @@ public class Cookie { private let fixedByteSize: Int32 = 56 var totalByteCount: Int32 { - return fixedByteSize + // expected-error {{the compiler is unable to type-check this expression in reasonable time}} + return fixedByteSize + (port != nil ? 2 : 0) + Int32(comment?.utf8.count ?? 0) + Int32(commentURL?.utf8.count ?? 0) + diff --git a/validation-test/Sema/type_checker_perf/slow/swift_package_index_4.swift b/validation-test/Sema/type_checker_perf/fast/swift_package_index_4.swift similarity index 94% rename from validation-test/Sema/type_checker_perf/slow/swift_package_index_4.swift rename to validation-test/Sema/type_checker_perf/fast/swift_package_index_4.swift index 8b05bedbea950..eaff8718abba3 100644 --- a/validation-test/Sema/type_checker_perf/slow/swift_package_index_4.swift +++ b/validation-test/Sema/type_checker_perf/fast/swift_package_index_4.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-scope-threshold=60000 +// RUN: %target-swift-frontend -typecheck %s -solver-scope-threshold=60000 // REQUIRES: tools-release,no_asan protocol ArgumentProtocol {} @@ -47,7 +47,7 @@ struct Options { static func evaluate(_ mode: CommandMode) -> Result { let defaultBuildDirectory = "" - return create // expected-error {{the compiler is unable to type-check this expression in reasonable time}} + return create <*> mode <| Option(key: "", defaultValue: nil, usage: "") <*> mode <| Option(key: "", defaultValue: nil, usage: "") <*> mode <| Option(key: "", defaultValue: FileManager.default.currentDirectoryPath, usage: "") diff --git a/validation-test/Sema/type_checker_perf/slow/swift_package_index_5.swift b/validation-test/Sema/type_checker_perf/fast/swift_package_index_5.swift similarity index 74% rename from validation-test/Sema/type_checker_perf/slow/swift_package_index_5.swift rename to validation-test/Sema/type_checker_perf/fast/swift_package_index_5.swift index e20ea384f9a5f..3f8bf5f2021c7 100644 --- a/validation-test/Sema/type_checker_perf/slow/swift_package_index_5.swift +++ b/validation-test/Sema/type_checker_perf/fast/swift_package_index_5.swift @@ -2,7 +2,7 @@ // REQUIRES: tools-release,no_asan func g(_: T) throws { - let _: T? = //expected-error {{the compiler is unable to type-check this expression in reasonable time}} + let _: T? = (try? f() as? T) ?? (try? f() as? T) ?? (try? f() as? T) ?? diff --git a/validation-test/Sema/type_checker_perf/slow/array_count_property_vs_method.swift b/validation-test/Sema/type_checker_perf/slow/array_count_property_vs_method.swift new file mode 100644 index 0000000000000..abcd1cfc545ce --- /dev/null +++ b/validation-test/Sema/type_checker_perf/slow/array_count_property_vs_method.swift @@ -0,0 +1,11 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=5000 +// REQUIRES: tools-release,no_asan +// REQUIRES: OS=macosx + +// FIXME: Array literals are considered "speculative" bindings at the moment but they cannot actually +// assume different types unlike integer and floating-pointer literals. +func f(n: Int, a: [Int]) { + // expected-error@+1 {{the compiler is unable to type-check this expression in reasonable time}} + let _ = [(0 ..< n + a.count).map { Int8($0) }] + + [(0 ..< n + a.count).map { Int8($0) }.reversed()] // Ok +} diff --git a/validation-test/Sema/type_checker_perf/fast/borderline_flat_map_operator_mix.swift b/validation-test/Sema/type_checker_perf/slow/borderline_flat_map_operator_mix.swift similarity index 50% rename from validation-test/Sema/type_checker_perf/fast/borderline_flat_map_operator_mix.swift rename to validation-test/Sema/type_checker_perf/slow/borderline_flat_map_operator_mix.swift index cca07954da499..b88a73d90121f 100644 --- a/validation-test/Sema/type_checker_perf/fast/borderline_flat_map_operator_mix.swift +++ b/validation-test/Sema/type_checker_perf/slow/borderline_flat_map_operator_mix.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=10 +// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 // REQUIRES: no_asan @@ -10,8 +10,11 @@ struct S { } } +// Note: One possible approach to this issue would be to determine when the array literal inside of the inner closure +// doesn't have any other possible bindings but Array and attempt it at that point. That would fail overload of flatMap +// that returns an optional value. func f(x: Array, y: Range) -> [S] { - return x.flatMap { z in + return x.flatMap { z in // expected-error {{the compiler is unable to type-check this expression in reasonable time}} return ((y.lowerBound / 1)...(y.upperBound + 1) / 1).flatMap { w in return [S(1 * Double(w) + 1.0 + z.t), S(1 * Double(w) + 1.0 - z.t)] diff --git a/validation-test/Sema/type_checker_perf/slow/collections_chained_with_plus.swift.gyb b/validation-test/Sema/type_checker_perf/slow/collections_chained_with_plus.swift.gyb new file mode 100644 index 0000000000000..82e558a5a73fb --- /dev/null +++ b/validation-test/Sema/type_checker_perf/slow/collections_chained_with_plus.swift.gyb @@ -0,0 +1,18 @@ +// RUN: %scale-test --invert-result --begin 1 --end 5 --step 1 --select NumLeafScopes %s -Xfrontend=-typecheck +// REQUIRES: asserts, no_asan + +struct Value: RandomAccessCollection, RangeReplaceableCollection { + let startIndex = 0 + let endIndex = 0 + + subscript(_: Int) -> Int { 0 } + + func replaceSubrange(_: Range, with: C) {} +} + +func f(v: Value) { + let _ = v +%for i in range(0, N): + + v +%end +} diff --git a/validation-test/Sema/type_checker_perf/slow/nil_coalescing_dictionary_values.swift.gyb b/validation-test/Sema/type_checker_perf/slow/nil_coalescing_dictionary_values.swift.gyb deleted file mode 100644 index 878c0a9dc78d1..0000000000000 --- a/validation-test/Sema/type_checker_perf/slow/nil_coalescing_dictionary_values.swift.gyb +++ /dev/null @@ -1,10 +0,0 @@ -// RUN: %scale-test --invert-result --begin 1 --end 8 --step 1 --select NumLeafScopes %s -Xfrontend=-typecheck -// REQUIRES: asserts, no_asan - -let x: Int? - -let _ = [ -%for i in range(0, N): - "k" : x ?? 0, -%end -] diff --git a/validation-test/Sema/type_checker_perf/slow/rdar26564101.swift b/validation-test/Sema/type_checker_perf/slow/rdar26564101.swift index a6a869ffb7ef7..e9e1d32c90f05 100644 --- a/validation-test/Sema/type_checker_perf/slow/rdar26564101.swift +++ b/validation-test/Sema/type_checker_perf/slow/rdar26564101.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 -disable-constraint-solver-performance-hacks +// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 // REQUIRES: tools-release,no_asan // UNSUPPORTED: swift_test_mode_optimize_none && OS=linux-gnu diff --git a/validation-test/Sema/type_checker_perf/slow/rdar31742586.swift b/validation-test/Sema/type_checker_perf/slow/rdar31742586.swift deleted file mode 100644 index 0cc33ce8253cf..0000000000000 --- a/validation-test/Sema/type_checker_perf/slow/rdar31742586.swift +++ /dev/null @@ -1,7 +0,0 @@ -// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1 -// REQUIRES: tools-release,no_asan - -func rdar31742586() -> Double { - return -(1 + 2) + -(3 + 4) + 5 - (-(1 + 2) + -(3 + 4) + 5) - // expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time}} -} diff --git a/validation-test/Sema/type_checker_perf/slow/swift_package_index_3.swift b/validation-test/Sema/type_checker_perf/slow/swift_package_index_3.swift index 2327507262418..2ef209606305e 100644 --- a/validation-test/Sema/type_checker_perf/slow/swift_package_index_3.swift +++ b/validation-test/Sema/type_checker_perf/slow/swift_package_index_3.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -solver-scope-threshold=50000 +// RUN: %target-typecheck-verify-swift %s -solver-scope-threshold=50000 // REQUIRES: tools-release,no_asan extension String { diff --git a/validation-test/compiler_crashers_2/35129b57d7eb80f4.swift b/validation-test/compiler_crashers_2_fixed/35129b57d7eb80f4.swift similarity index 70% rename from validation-test/compiler_crashers_2/35129b57d7eb80f4.swift rename to validation-test/compiler_crashers_2_fixed/35129b57d7eb80f4.swift index f1e36b4fb7ab4..043be0648f71a 100644 --- a/validation-test/compiler_crashers_2/35129b57d7eb80f4.swift +++ b/validation-test/compiler_crashers_2_fixed/35129b57d7eb80f4.swift @@ -1,3 +1,3 @@ // {"signature":"(anonymous namespace)::ExprWalker::rewriteTarget(swift::constraints::SyntacticElementTarget)"} -// RUN: not --crash %target-swift-frontend -typecheck %s +// RUN: not %target-swift-frontend -typecheck %s { Sendable(Sendable<