Skip to content

[ConstraintSystem] Return of the new disjunction favoring/selection algorithm #79461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 42 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b3f05e1
Revert "[ConstraintSystem] Revert new disjunction favoring algorithm …
xedin Feb 15, 2025
29eb601
[CSOptimizer] Improve contextual result type handling during overload…
xedin Feb 6, 2025
12955de
[CSOptimizer] Infer result types through ternary expressions
xedin Feb 6, 2025
f8059e6
[CSOptimizer] Infer argument candidates through optionals
xedin Feb 6, 2025
42aa2f7
[CSOptimizer] Allow matching against CGFloat as a contextual result type
xedin Feb 6, 2025
42c967c
[CSOptimizer] Infer types from init calls used as arguments
xedin Feb 6, 2025
1ecbabe
[CSOptimizer] Don't reduce the ranking for non-default literal types
xedin Feb 6, 2025
78e69a7
[CSOptimizer] Add support for `??` operator
xedin Feb 7, 2025
5050027
[Tests] NFC: Add a test-case with literal chain passed as an argument…
xedin Feb 8, 2025
fdbe334
[CSOptimizer] Emulate old hack for unary argument matching more preci…
xedin Feb 8, 2025
1704e50
[CSOptimizer] Always prefer a disjunction with a single active choice
xedin Feb 9, 2025
0aa06e0
[CSSolver] Attempt choices marked as disfavored at the end of partition
xedin Feb 9, 2025
2aa5ff4
[CSOptimizer] Expand literal support to bools, strings and dictionaries
xedin Feb 11, 2025
c191e59
[CSOptimizer] Support non-operator generic choices with simple signat…
xedin Feb 11, 2025
3b92fa8
[CSOptimizer] Remove `selectBestBindingDisjunction` hack
xedin Feb 16, 2025
8fecae7
[CSOptimizer] Introduce `_OptionalNilComparisonType` as candidate for…
xedin Feb 16, 2025
e06c5a5
[CSGen] Restore optimization that inferred a result type of `subscrip…
xedin Feb 16, 2025
811e108
[CSOptimizer] Allow matching candidates with optional types against g…
xedin Feb 17, 2025
fdcb376
[CSOptimizer] Introduce a drop of inference to preserved unary argume…
xedin Feb 17, 2025
f2be7ae
[CSOptimizer] Update candidate selection to use arithmetic operator c…
xedin Feb 18, 2025
0e3012c
[CSOptimizer] Reset the overall score of operator disjunctions that i…
xedin Feb 18, 2025
e03b275
[CSOptimizer] Introduce a way to preference disjunctions before score…
xedin Feb 18, 2025
1a1e6d7
[CSOptimizer] Prevent candidate inference from unresolved generic par…
xedin Feb 20, 2025
de94d0e
[Tests] NFC: Adjust scope threshold for rdar://rdar31742586 test-case
xedin Feb 21, 2025
a0e37d0
[CSOptimizer] Restrict unary argument legacy favoring behavior to `Ap…
xedin Feb 28, 2025
b2f7bff
[CSOptimizer] Account for speculative scores only when matching opera…
xedin Mar 2, 2025
93cfc00
[CSOptimizer] Fix scoring while matching against partially resolved p…
xedin Mar 2, 2025
be3cfbf
[CSOptimizer] Disable unary argument hack if overload set has require…
xedin Mar 3, 2025
4fc9481
[CSOptimizer] Detect when candidate comes from string interpolation
xedin Mar 4, 2025
fd15beb
[CSOptimizer] NFC: Use builder pattern to construct `DisjunctionInfo`
xedin Mar 4, 2025
da2207a
[CSOptimizer] Skip disfavored choices when they would result in a wor…
xedin Mar 10, 2025
2537bd1
[CSOptimizer] Account for the fact that sometimes all initializer cho…
xedin Mar 11, 2025
de559fc
[Tests] NFC: Move Double/CGFloat test because it tests local type met…
xedin Mar 11, 2025
0904ba8
[CSOptimizer] Don't attempt to walk into literals while analyzing ope…
xedin Mar 13, 2025
1be8601
[CSOptimizer] NFC: Adopt new `Constraint` and `ConstraintGraph` APIs
xedin Mar 15, 2025
b1956c2
[Tests] NFC: Remove FIXME and diagnostic from a fixed test-case
xedin Mar 15, 2025
a19a625
[CSOptimizer] Consider choices marked as `@_disfavoredOverload`
xedin Mar 15, 2025
c673b72
[CSOptimizer] NFC: Adopt to changes in `getParameterList()` API
xedin Apr 21, 2025
c9a9f1f
[CSOptimizer] NFC: Adopt to changes in `containsProtocol(...)` API
xedin Apr 21, 2025
09eb677
[CSOptimizer] Adopt to removal of `TypeBase::isArrayType()` by switch…
xedin May 17, 2025
0540a2b
[TypeChecker] Update `async` initializer warning downgrade to check t…
xedin May 17, 2025
02ab2dd
[Tests] NFC: Mark one of the fuzzer crashers as fixed
xedin Jun 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/swift/AST/KnownStdlibTypes.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 0 additions & 3 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -975,9 +975,6 @@ namespace swift {
/// is for testing purposes.
std::vector<std::string> DebugForbidTypecheckPrefixes;

/// Disable the shrink phase of the expression type checker.
bool SolverDisableShrink = false;

/// Enable experimental operator designated types feature.
bool EnableOperatorDesignatedTypes = false;

Expand Down
4 changes: 0 additions & 4 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -852,10 +852,6 @@ def solver_scope_threshold_EQ : Joined<["-"], "solver-scope-threshold=">,
def solver_trail_threshold_EQ : Joined<["-"], "solver-trail-threshold=">,
HelpText<"Expression type checking trail change limit">;

def solver_disable_shrink :
Flag<["-"], "solver-disable-shrink">,
HelpText<"Disable the shrink phase of expression type checking">;

def solver_disable_splitter : Flag<["-"], "solver-disable-splitter">,
HelpText<"Disable the component splitter phase of expression type checking">;

Expand Down
6 changes: 6 additions & 0 deletions include/swift/Sema/CSBindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,12 @@ class BindingSet {
void forEachLiteralRequirement(
llvm::function_ref<void(KnownProtocolKind)> callback) const;

void forEachAdjacentVariable(
llvm::function_ref<void(TypeVariableType *)> callback) const {
for (auto *typeVar : AdjacentVars)
callback(typeVar);
}

/// Return a literal requirement that has the most impact on the binding
/// score.
LiteralBindingKind getLiteralForScore() const;
Expand Down
5 changes: 0 additions & 5 deletions include/swift/Sema/Constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -852,11 +852,6 @@ class Constraint final : public llvm::ilist_node<Constraint>,
});
}

/// Returns the number of resolved argument types for an applied disjunction
/// constraint. This is always zero for disjunctions that do not represent
/// an applied overload.
unsigned countResolvedArgumentTypes(ConstraintSystem &cs) const;

/// Determine if this constraint represents explicit conversion,
/// e.g. coercion constraint "as X" which forms a disjunction.
bool isExplicitConversion() const;
Expand Down
131 changes: 31 additions & 100 deletions include/swift/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,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.
///
Expand Down Expand Up @@ -2260,10 +2272,6 @@ class ConstraintSystem {

llvm::SetVector<TypeVariableType *> TypeVariables;

/// Maps expressions to types for choosing a favored overload
/// type in a disjunction constraint.
llvm::DenseMap<Expr *, TypeBase *> FavoredTypes;

/// Maps discovered closures to their types inferred
/// from declared parameters/result and body.
///
Expand Down Expand Up @@ -2474,74 +2482,6 @@ class ConstraintSystem {
SynthesizedConformances;

private:
/// Describe the candidate expression for partial solving.
/// This class used by shrink & solve methods which apply
/// variation of directional path consistency algorithm in attempt
/// to reduce scopes of the overload sets (disjunctions) in the system.
class Candidate {
Expr *E;
DeclContext *DC;
llvm::BumpPtrAllocator &Allocator;

// Contextual Information.
Type CT;
ContextualTypePurpose CTP;

public:
Candidate(ConstraintSystem &cs, Expr *expr, Type ct = Type(),
ContextualTypePurpose ctp = ContextualTypePurpose::CTP_Unused)
: E(expr), DC(cs.DC), Allocator(cs.Allocator), CT(ct), CTP(ctp) {}

/// Return underlying expression.
Expr *getExpr() const { return E; }

/// Try to solve this candidate sub-expression
/// and re-write it's OSR domains afterwards.
///
/// \param shrunkExprs The set of expressions which
/// domains have been successfully shrunk so far.
///
/// \returns true on solver failure, false otherwise.
bool solve(llvm::SmallSetVector<OverloadSetRefExpr *, 4> &shrunkExprs);

/// Apply solutions found by solver as reduced OSR sets for
/// for current and all of it's sub-expressions.
///
/// \param solutions The solutions found by running solver on the
/// this candidate expression.
///
/// \param shrunkExprs The set of expressions which
/// domains have been successfully shrunk so far.
void applySolutions(
llvm::SmallVectorImpl<Solution> &solutions,
llvm::SmallSetVector<OverloadSetRefExpr *, 4> &shrunkExprs) const;

/// Check if attempt at solving of the candidate makes sense given
/// the current conditions - number of shrunk domains which is related
/// to the given candidate over the total number of disjunctions present.
static bool
isTooComplexGiven(ConstraintSystem *const cs,
llvm::SmallSetVector<OverloadSetRefExpr *, 4> &shrunkExprs) {
SmallVector<Constraint *, 8> disjunctions;
cs->collectDisjunctions(disjunctions);

unsigned unsolvedDisjunctions = disjunctions.size();
for (auto *disjunction : disjunctions) {
auto *locator = disjunction->getLocator();
if (!locator)
continue;

if (auto *OSR = getAsExpr<OverloadSetRefExpr>(locator->getAnchor())) {
if (shrunkExprs.count(OSR) > 0)
--unsolvedDisjunctions;
}
}

// The threshold used to be `TypeCheckerOpts.SolverShrinkUnsolvedThreshold`
return unsolvedDisjunctions >= 10;
}
};

/// Describes the current solver state.
struct SolverState {
SolverState(ConstraintSystem &cs,
Expand Down Expand Up @@ -3065,15 +3005,6 @@ class ConstraintSystem {
return nullptr;
}

TypeBase* getFavoredType(Expr *E) {
assert(E != nullptr);
return this->FavoredTypes[E];
}
void setFavoredType(Expr *E, TypeBase *T) {
assert(E != nullptr);
this->FavoredTypes[E] = T;
}

/// Set the type in our type map for the given node, and record the change
/// in the trail.
///
Expand Down Expand Up @@ -5376,19 +5307,11 @@ class ConstraintSystem {
/// \returns true if an error occurred, false otherwise.
bool solveSimplified(SmallVectorImpl<Solution> &solutions);

/// Find reduced domains of disjunction constraints for given
/// expression, this is achieved to solving individual sub-expressions
/// and combining resolving types. Such algorithm is called directional
/// path consistency because it goes from children to parents for all
/// related sub-expressions taking union of their domains.
///
/// \param expr The expression to find reductions for.
void shrink(Expr *expr);

/// Pick a disjunction from the InactiveConstraints list.
///
/// \returns The selected disjunction.
Constraint *selectDisjunction();
/// \returns The selected disjunction and a set of it's favored choices.
std::optional<std::pair<Constraint *, llvm::TinyPtrVector<Constraint *>>>
selectDisjunction();

/// Pick a conjunction from the InactiveConstraints list.
///
Expand Down Expand Up @@ -5577,11 +5500,6 @@ class ConstraintSystem {
bool applySolutionToBody(TapExpr *tapExpr,
SyntacticElementTargetRewriter &rewriter);

/// Reorder the disjunctive clauses for a given expression to
/// increase the likelihood that a favored constraint will be successfully
/// resolved before any others.
void optimizeConstraints(Expr *e);

void startExpressionTimer(ExpressionTimer::AnchorType anchor);

/// Determine if we've already explored too many paths in an
Expand Down Expand Up @@ -6088,6 +6006,12 @@ class DisjunctionChoice {
return false;
}

bool isDisfavored() const {
if (auto *decl = getOverloadChoiceDecl(Choice))
return decl->getAttrs().hasAttribute<DisfavoredOverloadAttr>();
return false;
}

bool isBeginningOfPartition() const { return IsBeginningOfPartition; }

// FIXME: All three of the accessors below are required to support
Expand Down Expand Up @@ -6322,7 +6246,8 @@ class DisjunctionChoiceProducer : public BindingProducer<DisjunctionChoice> {
public:
using Element = DisjunctionChoice;

DisjunctionChoiceProducer(ConstraintSystem &cs, Constraint *disjunction)
DisjunctionChoiceProducer(ConstraintSystem &cs, Constraint *disjunction,
llvm::TinyPtrVector<Constraint *> &favorites)
: BindingProducer(cs, disjunction->shouldRememberChoice()
? disjunction->getLocator()
: nullptr),
Expand All @@ -6332,6 +6257,11 @@ class DisjunctionChoiceProducer : public BindingProducer<DisjunctionChoice> {
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);
}
Expand Down Expand Up @@ -6376,8 +6306,9 @@ class DisjunctionChoiceProducer : public BindingProducer<DisjunctionChoice> {
// 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<unsigned> &Ordering,
SmallVectorImpl<unsigned> &PartitionBeginning);
void
partitionDisjunction(SmallVectorImpl<unsigned> &Ordering,
SmallVectorImpl<unsigned> &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
Expand Down
3 changes: 0 additions & 3 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2015,9 +2015,6 @@ static bool ParseTypeCheckerArgs(TypeCheckerOptions &Opts, ArgList &Args,
Opts.DebugForbidTypecheckPrefixes.push_back(A);
}

if (Args.getLastArg(OPT_solver_disable_shrink))
Opts.SolverDisableShrink = true;

if (Args.getLastArg(OPT_solver_disable_splitter))
Opts.SolverDisableSplitter = true;

Expand Down
1 change: 1 addition & 0 deletions lib/Sema/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ add_swift_host_library(swiftSema STATIC
CSStep.cpp
CSTrail.cpp
CSFix.cpp
CSOptimizer.cpp
CSDiagnostics.cpp
CodeSynthesis.cpp
CodeSynthesisDistributedActor.cpp
Expand Down
33 changes: 31 additions & 2 deletions lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,19 @@ using namespace swift;
using namespace constraints;
using namespace inference;


void ConstraintGraphNode::initBindingSet() {
ASSERT(!hasBindingSet());
ASSERT(forRepresentativeVar());

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<Type> checkTypeOfBinding(TypeVariableType *typeVar,
Type type);

Expand Down Expand Up @@ -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.
Expand Down
Loading