Skip to content

Commit 1359840

Browse files
authored
Merge pull request #58400 from xedin/too-complex-improvements
[ConstraintSystem] Improve precision of "too complex" diagnostic
2 parents 05c6c90 + 7869b73 commit 1359840

File tree

10 files changed

+96
-41
lines changed

10 files changed

+96
-41
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ class ExpressionTimer {
238238

239239
AnchorType getAnchor() const { return Anchor; }
240240

241+
SourceRange getAffectedRange() const;
242+
241243
unsigned getWarnLimit() const {
242244
return Context.TypeCheckerOpts.WarnLongExpressionTypeChecking;
243245
}
@@ -5459,9 +5461,17 @@ class ConstraintSystem {
54595461

54605462
/// Determine if we've already explored too many paths in an
54615463
/// attempt to solve this expression.
5462-
bool isExpressionAlreadyTooComplex = false;
5463-
bool getExpressionTooComplex(size_t solutionMemory) {
5464-
if (isExpressionAlreadyTooComplex)
5464+
std::pair<bool, SourceRange> isAlreadyTooComplex = {false, SourceRange()};
5465+
5466+
/// If optional is not nil, result is guaranteed to point at a valid
5467+
/// location.
5468+
Optional<SourceRange> getTooComplexRange() const {
5469+
auto range = isAlreadyTooComplex.second;
5470+
return range.isValid() ? range : Optional<SourceRange>();
5471+
}
5472+
5473+
bool isTooComplex(size_t solutionMemory) {
5474+
if (isAlreadyTooComplex.first)
54655475
return true;
54665476

54675477
auto CancellationFlag = getASTContext().CancellationFlag;
@@ -5472,7 +5482,9 @@ class ConstraintSystem {
54725482
MaxMemory = std::max(used, MaxMemory);
54735483
auto threshold = getASTContext().TypeCheckerOpts.SolverMemoryThreshold;
54745484
if (MaxMemory > threshold) {
5475-
return isExpressionAlreadyTooComplex= true;
5485+
// No particular location for OoM problems.
5486+
isAlreadyTooComplex.first = true;
5487+
return true;
54765488
}
54775489

54785490
if (Timer && Timer->isExpired()) {
@@ -5481,27 +5493,29 @@ class ConstraintSystem {
54815493
// emitting an error.
54825494
Timer->disableWarning();
54835495

5484-
return isExpressionAlreadyTooComplex = true;
5496+
isAlreadyTooComplex = {true, Timer->getAffectedRange()};
5497+
return true;
54855498
}
54865499

54875500
// Bail out once we've looked at a really large number of
54885501
// choices.
54895502
if (CountScopes > getASTContext().TypeCheckerOpts.SolverBindingThreshold) {
5490-
return isExpressionAlreadyTooComplex = true;
5503+
isAlreadyTooComplex.first = true;
5504+
return true;
54915505
}
54925506

54935507
return false;
54945508
}
54955509

5496-
bool getExpressionTooComplex(SmallVectorImpl<Solution> const &solutions) {
5497-
if (isExpressionAlreadyTooComplex)
5510+
bool isTooComplex(SmallVectorImpl<Solution> const &solutions) {
5511+
if (isAlreadyTooComplex.first)
54985512
return true;
54995513

55005514
size_t solutionMemory = 0;
55015515
for (auto const& s : solutions) {
55025516
solutionMemory += s.getTotalMemory();
55035517
}
5504-
return getExpressionTooComplex(solutionMemory);
5518+
return isTooComplex(solutionMemory);
55055519
}
55065520

55075521
// If the given constraint is an applied disjunction, get the argument function

include/swift/Sema/SolutionResult.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class SolutionResult {
6464
/// \c numSolutions entries.
6565
Solution *solutions = nullptr;
6666

67+
/// A source range that was too complex to solve.
68+
Optional<SourceRange> TooComplexAt = None;
69+
6770
/// General constructor for the named constructors.
6871
SolutionResult(Kind kind) : kind(kind) {
6972
emittedDiagnostic = false;
@@ -95,9 +98,7 @@ class SolutionResult {
9598

9699
/// Produce a "too complex" failure, which was not yet been
97100
/// diagnosed.
98-
static SolutionResult forTooComplex() {
99-
return SolutionResult(TooComplex);
100-
}
101+
static SolutionResult forTooComplex(Optional<SourceRange> affected);
101102

102103
/// Produce a failure that has already been diagnosed.
103104
static SolutionResult forError() {
@@ -123,6 +124,10 @@ class SolutionResult {
123124
/// Take the set of solutions when there is an ambiguity.
124125
MutableArrayRef<Solution> takeAmbiguousSolutions() &&;
125126

127+
/// Retrieve a range of source that has been determined to be too
128+
/// complex to solve in a reasonable time.
129+
Optional<SourceRange> getTooComplexAt() const { return TooComplexAt; }
130+
126131
/// Whether this solution requires the client to produce a diagnostic.
127132
bool requiresDiagnostic() const {
128133
switch (kind) {

lib/Sema/CSApply.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9106,6 +9106,12 @@ SolutionResult SolutionResult::forAmbiguous(
91069106
return result;
91079107
}
91089108

9109+
SolutionResult SolutionResult::forTooComplex(Optional<SourceRange> affected) {
9110+
SolutionResult result(Kind::TooComplex);
9111+
result.TooComplexAt = affected;
9112+
return result;
9113+
}
9114+
91099115
SolutionResult::~SolutionResult() {
91109116
assert((!requiresDiagnostic() || emittedDiagnostic) &&
91119117
"SolutionResult was destroyed without emitting a diagnostic");

lib/Sema/CSSolver.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,12 +1298,21 @@ Optional<std::vector<Solution>> ConstraintSystem::solve(
12981298
maybeProduceFallbackDiagnostic(target);
12991299
return None;
13001300

1301-
case SolutionResult::TooComplex:
1302-
getASTContext().Diags.diagnose(
1303-
target.getLoc(), diag::expression_too_complex)
1304-
.highlight(target.getSourceRange());
1301+
case SolutionResult::TooComplex: {
1302+
auto affectedRange = solution.getTooComplexAt();
1303+
1304+
// If affected range is unknown, let's use whole
1305+
// target.
1306+
if (!affectedRange)
1307+
affectedRange = target.getSourceRange();
1308+
1309+
getASTContext()
1310+
.Diags.diagnose(affectedRange->Start, diag::expression_too_complex)
1311+
.highlight(*affectedRange);
1312+
13051313
solution.markAsDiagnosed();
13061314
return None;
1315+
}
13071316

13081317
case SolutionResult::Ambiguous:
13091318
// If salvaging produced an ambiguous result, it has already been
@@ -1374,8 +1383,8 @@ ConstraintSystem::solveImpl(SolutionApplicationTarget &target,
13741383
SmallVector<Solution, 4> solutions;
13751384
solve(solutions, allowFreeTypeVariables);
13761385

1377-
if (getExpressionTooComplex(solutions))
1378-
return SolutionResult::forTooComplex();
1386+
if (isTooComplex(solutions))
1387+
return SolutionResult::forTooComplex(getTooComplexRange());
13791388

13801389
switch (solutions.size()) {
13811390
case 0:
@@ -1415,7 +1424,7 @@ bool ConstraintSystem::solve(SmallVectorImpl<Solution> &solutions,
14151424
filterSolutions(solutions);
14161425

14171426
// We fail if there is no solution or the expression was too complex.
1418-
return solutions.empty() || getExpressionTooComplex(solutions);
1427+
return solutions.empty() || isTooComplex(solutions);
14191428
}
14201429

14211430
void ConstraintSystem::solveImpl(SmallVectorImpl<Solution> &solutions) {

lib/Sema/CSStep.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ bool SplitterStep::mergePartialSolutions() const {
251251

252252
// Since merging partial solutions can go exponential, make sure we didn't
253253
// pass the "too complex" thresholds including allocated memory and time.
254-
if (CS.getExpressionTooComplex(solutionMemory))
254+
if (CS.isTooComplex(solutionMemory))
255255
return false;
256256

257257
} while (nextCombination(counts, indices));
@@ -313,7 +313,7 @@ StepResult ComponentStep::take(bool prevFailed) {
313313
// One of the previous components created by "split"
314314
// failed, it means that we can't solve this component.
315315
if ((prevFailed && DependsOnPartialSolutions.empty()) ||
316-
CS.getExpressionTooComplex(Solutions))
316+
CS.isTooComplex(Solutions))
317317
return done(/*isSuccess=*/false);
318318

319319
// Setup active scope, only if previous component didn't fail.

lib/Sema/CSStep.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ template <typename P> class BindingStep : public SolverStep {
506506
StepResult take(bool prevFailed) override {
507507
// Before attempting the next choice, let's check whether the constraint
508508
// system is too complex already.
509-
if (CS.getExpressionTooComplex(Solutions))
509+
if (CS.isTooComplex(Solutions))
510510
return done(/*isSuccess=*/false);
511511

512512
while (auto choice = Producer()) {

lib/Sema/ConstraintSystem.cpp

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ ExpressionTimer::ExpressionTimer(AnchorType Anchor, ConstraintSystem &CS,
5656
PrintDebugTiming(CS.getASTContext().TypeCheckerOpts.DebugTimeExpressions),
5757
PrintWarning(true) {}
5858

59+
SourceRange ExpressionTimer::getAffectedRange() const {
60+
ASTNode anchor;
61+
62+
if (auto *locator = Anchor.dyn_cast<ConstraintLocator *>()) {
63+
anchor = simplifyLocatorToAnchor(locator);
64+
// If locator couldn't be simplified down to a single AST
65+
// element, let's use its root.
66+
if (!anchor)
67+
anchor = locator->getAnchor();
68+
} else {
69+
anchor = Anchor.get<Expr *>();
70+
}
71+
72+
return anchor.getSourceRange();
73+
}
74+
5975
ExpressionTimer::~ExpressionTimer() {
6076
auto elapsed = getElapsedProcessTimeInFractionalSeconds();
6177
unsigned elapsedMS = static_cast<unsigned>(elapsed * 1000);
@@ -81,22 +97,13 @@ ExpressionTimer::~ExpressionTimer() {
8197
if (WarnLimit == 0 || elapsedMS < WarnLimit)
8298
return;
8399

84-
ASTNode anchor;
85-
if (auto *locator = Anchor.dyn_cast<ConstraintLocator *>()) {
86-
anchor = simplifyLocatorToAnchor(locator);
87-
// If locator couldn't be simplified down to a single AST
88-
// element, let's warn about its root.
89-
if (!anchor)
90-
anchor = locator->getAnchor();
91-
} else {
92-
anchor = Anchor.get<Expr *>();
93-
}
100+
auto sourceRange = getAffectedRange();
94101

95-
if (anchor.getStartLoc().isValid()) {
102+
if (sourceRange.Start.isValid()) {
96103
Context.Diags
97-
.diagnose(anchor.getStartLoc(), diag::debug_long_expression, elapsedMS,
104+
.diagnose(sourceRange.Start, diag::debug_long_expression, elapsedMS,
98105
WarnLimit)
99-
.highlight(anchor.getSourceRange());
106+
.highlight(sourceRange);
100107
}
101108
}
102109

@@ -3776,8 +3783,8 @@ SolutionResult ConstraintSystem::salvage() {
37763783
// Fall through to produce diagnostics.
37773784
}
37783785

3779-
if (getExpressionTooComplex(viable))
3780-
return SolutionResult::forTooComplex();
3786+
if (isTooComplex(viable))
3787+
return SolutionResult::forTooComplex(getTooComplexRange());
37813788

37823789
// Could not produce a specific diagnostic; punt to the client.
37833790
return SolutionResult::forUndiagnosedError();

validation-test/Sema/type_checker_perf/slow/rdar19612086.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ struct rdar19612086 {
1111
let x = 1.0
1212

1313
var description : String {
14-
return "\(i)" + Stringly(format: "%.2f", x) +
14+
return "\(i)" + Stringly(format: "%.2f", x) + // expected-error {{reasonable time}}
1515
"\(i+1)" + Stringly(format: "%.2f", x) +
1616
"\(i+2)" + Stringly(format: "%.2f", x) +
1717
"\(i+3)" + Stringly(format: "%.2f", x) +
1818
"\(i+4)" + Stringly(format: "%.2f", x) +
1919
"\(i+5)" + Stringly(format: "%.2f", x)
20-
// expected-error@-1 {{reasonable time}}
2120
}
2221
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1
22
// REQUIRES: tools-release,no_asan
33

4-
let _ = (0...1).lazy.flatMap {
4+
let _ = (0...1).lazy.flatMap { // expected-error {{reasonable time}}
55
a in (1...2).lazy.map { b in (a, b) }
66
}.filter {
7-
// expected-error@-1 {{reasonable time}}
87
1 < $0 && $0 < $1 && $0 + $1 < 3
98
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1
2+
// REQUIRES: tools-release,no_asan
3+
4+
func compute(_ cont: () -> Void) {}
5+
6+
func test() {
7+
compute {
8+
let x = 42
9+
compute {
10+
print(x)
11+
let v: UInt64 = UInt64((24 / UInt32(1)) + UInt32(0) - UInt32(0) - 24 / 42 - 42)
12+
// expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions}}
13+
print(v)
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)