Skip to content

Commit 094a781

Browse files
committed
[TypeChecker] Always emit a fallback error if type-check failed without producing one
Sometimes constraint solver fails without producing any diagnostics, it could happen during different phases e.g. pre-check, constraint generation, or even while attempting to apply solution. Such behavior leads to crashes down the line in AST Verifier or SILGen which are hard to diagnose. Let's guard against that by tracking if solver produced any diagnostics upon its failure and if no errors were or are scheduled to be produced, let's produce a fallback fatal error pointing at affected expression. Resolves: rdar://problem/38885760 (cherry picked from commit 35202ab)
1 parent daac347 commit 094a781

File tree

7 files changed

+169
-16
lines changed

7 files changed

+169
-16
lines changed

include/swift/AST/ASTContext.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,10 @@ class ASTContext final {
765765
bool IsError;
766766
};
767767

768+
/// Check whether current context has any errors associated with
769+
/// ill-formed protocol conformances which haven't been produced yet.
770+
bool hasDelayedConformanceErrors() const;
771+
768772
/// Add a delayed diagnostic produced while type-checking a
769773
/// particular protocol conformance.
770774
void addDelayedConformanceDiag(NormalProtocolConformance *conformance,

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2821,6 +2821,10 @@ ERROR(type_of_expression_is_ambiguous,none,
28212821
ERROR(specific_type_of_expression_is_ambiguous,none,
28222822
"expression type %0 is ambiguous without more context", (Type))
28232823

2824+
ERROR(failed_to_produce_diagnostic,Fatal,
2825+
"failed to produce diagnostic for expression; "
2826+
"please file a bug report with your project", ())
2827+
28242828

28252829
ERROR(missing_protocol,none,
28262830
"missing protocol %0", (Identifier))

lib/AST/ASTContext.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2047,6 +2047,19 @@ LazyGenericContextData *ASTContext::getOrCreateLazyGenericContextData(
20472047
lazyLoader);
20482048
}
20492049

2050+
bool ASTContext::hasDelayedConformanceErrors() const {
2051+
for (const auto &entry : getImpl().DelayedConformanceDiags) {
2052+
auto &diagnostics = entry.getSecond();
2053+
if (std::any_of(diagnostics.begin(), diagnostics.end(),
2054+
[](const ASTContext::DelayedConformanceDiag &diag) {
2055+
return diag.IsError;
2056+
}))
2057+
return true;
2058+
}
2059+
2060+
return false;
2061+
}
2062+
20502063
void ASTContext::addDelayedConformanceDiag(
20512064
NormalProtocolConformance *conformance,
20522065
DelayedConformanceDiag fn) {

lib/Sema/CSDiag.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,6 +1893,10 @@ Expr *FailureDiagnosis::typeCheckChildIndependently(
18931893
// the context is missing).
18941894
TypeCheckExprOptions TCEOptions = TypeCheckExprFlags::DisableStructuralChecks;
18951895

1896+
// Make sure that typechecker knows that this is an attempt
1897+
// to diagnose a problem.
1898+
TCEOptions |= TypeCheckExprFlags::SubExpressionDiagnostics;
1899+
18961900
// Don't walk into non-single expression closure bodies, because
18971901
// ExprTypeSaver and TypeNullifier skip them too.
18981902
TCEOptions |= TypeCheckExprFlags::SkipMultiStmtClosures;

lib/Sema/CSSolver.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,8 @@ ConstraintSystem::solveImpl(Expr *&expr,
11751175
if (auto generatedExpr = generateConstraints(expr))
11761176
expr = generatedExpr;
11771177
else {
1178+
if (listener)
1179+
listener->constraintGenerationFailed(expr);
11781180
return SolutionKind::Error;
11791181
}
11801182

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 114 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "swift/AST/NameLookup.h"
3030
#include "swift/AST/ParameterList.h"
3131
#include "swift/AST/PrettyStackTrace.h"
32+
#include "swift/AST/ProtocolConformance.h"
3233
#include "swift/AST/SubstitutionMap.h"
3334
#include "swift/AST/TypeCheckerDebugConsumer.h"
3435
#include "swift/Basic/Statistic.h"
@@ -1937,6 +1938,11 @@ Expr *ExprTypeCheckListener::appliedSolution(Solution &solution, Expr *expr) {
19371938
return expr;
19381939
}
19391940

1941+
void ExprTypeCheckListener::preCheckFailed(Expr *expr) {}
1942+
void ExprTypeCheckListener::constraintGenerationFailed(Expr *expr) {}
1943+
void ExprTypeCheckListener::applySolutionFailed(Solution &solution,
1944+
Expr *expr) {}
1945+
19401946
void ParentConditionalConformance::diagnoseConformanceStack(
19411947
DiagnosticEngine &diags, SourceLoc loc,
19421948
ArrayRef<ParentConditionalConformance> conformances) {
@@ -1964,20 +1970,116 @@ bool GenericRequirementsCheckListener::diagnoseUnsatisfiedRequirement(
19641970
return false;
19651971
}
19661972

1973+
/// Sometimes constraint solver fails without producing any diagnostics,
1974+
/// that leads to crashes down the line in AST Verifier or SILGen
1975+
/// which, as a result, are much harder to figure out.
1976+
///
1977+
/// This class is intended to guard against situations like that by
1978+
/// keeping track of failures of different type-check phases, and
1979+
/// emitting fallback fatal error if any of them fail without producing
1980+
/// error diagnostic, and there were no errors emitted or scheduled to be
1981+
/// emitted previously.
1982+
class FallbackDiagnosticListener : public ExprTypeCheckListener {
1983+
TypeChecker &TC;
1984+
TypeCheckExprOptions Options;
1985+
ExprTypeCheckListener *BaseListener;
1986+
1987+
public:
1988+
FallbackDiagnosticListener(TypeChecker &TC, TypeCheckExprOptions options,
1989+
ExprTypeCheckListener *base)
1990+
: TC(TC), Options(options), BaseListener(base) {}
1991+
1992+
bool builtConstraints(ConstraintSystem &cs, Expr *expr) override {
1993+
return BaseListener ? BaseListener->builtConstraints(cs, expr) : false;
1994+
}
1995+
1996+
Expr *foundSolution(Solution &solution, Expr *expr) override {
1997+
return BaseListener ? BaseListener->foundSolution(solution, expr) : expr;
1998+
}
1999+
2000+
Expr *appliedSolution(Solution &solution, Expr *expr) override {
2001+
return BaseListener ? BaseListener->appliedSolution(solution, expr) : expr;
2002+
}
2003+
2004+
void preCheckFailed(Expr *expr) override {
2005+
if (BaseListener)
2006+
BaseListener->preCheckFailed(expr);
2007+
maybeProduceFallbackDiagnostic(expr);
2008+
}
2009+
2010+
void constraintGenerationFailed(Expr *expr) override {
2011+
if (BaseListener)
2012+
BaseListener->constraintGenerationFailed(expr);
2013+
maybeProduceFallbackDiagnostic(expr);
2014+
}
2015+
2016+
void applySolutionFailed(Solution &solution, Expr *expr) override {
2017+
if (BaseListener)
2018+
BaseListener->applySolutionFailed(solution, expr);
2019+
2020+
if (hadAnyErrors())
2021+
return;
2022+
2023+
// If solution involves invalid or incomplete conformances that's
2024+
// a probable cause of failure to apply it without producing an error,
2025+
// which is going to be diagnosed later, so let's not produce
2026+
// fallback diagnostic in this case.
2027+
if (llvm::any_of(
2028+
solution.Conformances,
2029+
[](const std::pair<ConstraintLocator *, ProtocolConformanceRef>
2030+
&conformance) -> bool {
2031+
auto &ref = conformance.second;
2032+
return ref.isConcrete() && (ref.getConcrete()->isInvalid() ||
2033+
ref.getConcrete()->isIncomplete());
2034+
}))
2035+
return;
2036+
2037+
maybeProduceFallbackDiagnostic(expr);
2038+
}
2039+
2040+
private:
2041+
bool hadAnyErrors() const { return TC.Context.Diags.hadAnyError(); }
2042+
2043+
void maybeProduceFallbackDiagnostic(Expr *expr) const {
2044+
if (Options.contains(TypeCheckExprFlags::SubExpressionDiagnostics) ||
2045+
Options.contains(TypeCheckExprFlags::SuppressDiagnostics))
2046+
return;
2047+
2048+
// Before producing fatal error here, let's check if there are any "error"
2049+
// diagnostics already emitted or waiting to be emitted. Because they are
2050+
// a better indication of the problem.
2051+
if (!(hadAnyErrors() || TC.Context.hasDelayedConformanceErrors()))
2052+
TC.diagnose(expr->getLoc(), diag::failed_to_produce_diagnostic);
2053+
}
2054+
};
2055+
19672056
#pragma mark High-level entry points
19682057
Type TypeChecker::typeCheckExpression(Expr *&expr, DeclContext *dc,
19692058
TypeLoc convertType,
19702059
ContextualTypePurpose convertTypePurpose,
19712060
TypeCheckExprOptions options,
19722061
ExprTypeCheckListener *listener,
19732062
ConstraintSystem *baseCS) {
2063+
FallbackDiagnosticListener diagListener(*this, options, listener);
2064+
return typeCheckExpressionImpl(expr, dc, convertType, convertTypePurpose,
2065+
options, diagListener, baseCS);
2066+
}
2067+
2068+
Type TypeChecker::typeCheckExpressionImpl(Expr *&expr, DeclContext *dc,
2069+
TypeLoc convertType,
2070+
ContextualTypePurpose convertTypePurpose,
2071+
TypeCheckExprOptions options,
2072+
ExprTypeCheckListener &listener,
2073+
ConstraintSystem *baseCS) {
19742074
FrontendStatsTracer StatsTracer(Context.Stats, "typecheck-expr", expr);
19752075
PrettyStackTraceExpr stackTrace(Context, "type-checking", expr);
19762076

19772077
// First, pre-check the expression, validating any types that occur in the
19782078
// expression and folding sequence expressions.
1979-
if (preCheckExpression(expr, dc))
2079+
if (preCheckExpression(expr, dc)) {
2080+
listener.preCheckFailed(expr);
19802081
return Type();
2082+
}
19812083

19822084
// Construct a constraint system from this expression.
19832085
ConstraintSystemOptions csOptions = ConstraintSystemFlags::AllowFixes;
@@ -2032,10 +2134,9 @@ Type TypeChecker::typeCheckExpression(Expr *&expr, DeclContext *dc,
20322134
convertTo = getOptionalType(expr->getLoc(), var);
20332135
}
20342136

2035-
// Attempt to solve the constraint system.
20362137
SmallVector<Solution, 4> viable;
2037-
if (cs.solve(expr, convertTo, listener, viable,
2038-
allowFreeTypeVariables))
2138+
// Attempt to solve the constraint system.
2139+
if (cs.solve(expr, convertTo, &listener, viable, allowFreeTypeVariables))
20392140
return Type();
20402141

20412142
// If the client allows the solution to have unresolved type expressions,
@@ -2049,11 +2150,9 @@ Type TypeChecker::typeCheckExpression(Expr *&expr, DeclContext *dc,
20492150

20502151
auto result = expr;
20512152
auto &solution = viable[0];
2052-
if (listener) {
2053-
result = listener->foundSolution(solution, result);
2054-
if (!result)
2055-
return Type();
2056-
}
2153+
result = listener.foundSolution(solution, result);
2154+
if (!result)
2155+
return Type();
20572156

20582157
if (options.contains(TypeCheckExprFlags::SkipApplyingSolution))
20592158
return solution.simplifyType(cs.getType(expr));
@@ -2063,18 +2162,17 @@ Type TypeChecker::typeCheckExpression(Expr *&expr, DeclContext *dc,
20632162
solution, result, convertType.getType(),
20642163
options.contains(TypeCheckExprFlags::IsDiscarded),
20652164
options.contains(TypeCheckExprFlags::SkipMultiStmtClosures));
2165+
20662166
if (!result) {
2167+
listener.applySolutionFailed(solution, expr);
20672168
// Failure already diagnosed, above, as part of applying the solution.
20682169
return Type();
20692170
}
20702171

2071-
// If there's a listener, notify it that we've applied the solution.
2072-
if (listener) {
2073-
result = listener->appliedSolution(solution, result);
2074-
if (!result) {
2075-
return Type();
2076-
}
2077-
}
2172+
// Notify listener that we've applied the solution.
2173+
result = listener.appliedSolution(solution, result);
2174+
if (!result)
2175+
return Type();
20782176

20792177
if (getLangOpts().DebugConstraintSolver) {
20802178
auto &log = Context.TypeCheckerDebug->getStream();

lib/Sema/TypeChecker.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,14 @@ enum class TypeCheckExprFlags {
267267
/// If set, a conversion constraint should be specfied so that the result of
268268
/// the expression is an optional type.
269269
ExpressionTypeMustBeOptional = 0x400,
270+
271+
/// FIXME(diagnostics): Once diagnostics are completely switched to new
272+
/// framework, this flag could be removed as obsolete.
273+
///
274+
/// If set, this is a sub-expression, and it is being re-typechecked
275+
/// as part of the expression diagnostics, which is attempting to narrow
276+
/// down failure location.
277+
SubExpressionDiagnostics = 0x800,
270278
};
271279

272280
using TypeCheckExprOptions = OptionSet<TypeCheckExprFlags>;
@@ -385,6 +393,18 @@ class ExprTypeCheckListener {
385393
/// failure.
386394
virtual Expr *appliedSolution(constraints::Solution &solution,
387395
Expr *expr);
396+
397+
/// Callback invoked if expression is structurally unsound and can't
398+
/// be correctly processed by the constraint solver.
399+
virtual void preCheckFailed(Expr *expr);
400+
401+
/// Callback invoked if constraint system failed to generate
402+
/// constraints for a given expression.
403+
virtual void constraintGenerationFailed(Expr *expr);
404+
405+
/// Callback invoked if application of chosen solution to
406+
/// expression has failed.
407+
virtual void applySolutionFailed(constraints::Solution &solution, Expr *expr);
388408
};
389409

390410
/// A conditional conformance that implied some other requirements. That is, \c
@@ -1369,7 +1389,15 @@ class TypeChecker final : public LazyResolver {
13691389
TypeCheckExprOptions(), listener);
13701390
}
13711391

1392+
private:
1393+
Type typeCheckExpressionImpl(Expr *&expr, DeclContext *dc,
1394+
TypeLoc convertType,
1395+
ContextualTypePurpose convertTypePurpose,
1396+
TypeCheckExprOptions options,
1397+
ExprTypeCheckListener &listener,
1398+
constraints::ConstraintSystem *baseCS);
13721399

1400+
public:
13731401
/// \brief Type check the given expression and return its type without
13741402
/// applying the solution.
13751403
///

0 commit comments

Comments
 (0)