Skip to content

Commit 938d147

Browse files
committed
[TypeChecker] SE-0352: Require coercion if result type contains existential(s) that would loose generic requirements
Implements SE-0352 revision - require explicit `as` coercion when existential erasure loses information. For example: ```swift protocol P { associatedtype A } protocol Q { associatedtype B: P where B.A == Int } func getB<T: Q>(_: T) -> T.B { ... } func test(v: any Q) { let _ = getB(v) // <- produces `any P` which loses A == Int } ```
1 parent 863da1b commit 938d147

File tree

9 files changed

+500
-3
lines changed

9 files changed

+500
-3
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6357,5 +6357,19 @@ ERROR(attr_incompatible_with_back_deploy,none,
63576357
"'%0' cannot be applied to a back deployed %1",
63586358
(DeclAttribute, DescriptiveDeclKind))
63596359

6360+
//------------------------------------------------------------------------------
6361+
// MARK: Implicit opening of existential types
6362+
//------------------------------------------------------------------------------
6363+
6364+
ERROR(result_requires_explicit_coercion,none,
6365+
"inferred result type %0 requires explicit coercion due to "
6366+
"loss of generic requirements",
6367+
(Type))
6368+
6369+
NOTE(candidate_result_requires_explicit_coercion,none,
6370+
"inferred result type %0 requires explicit coercion due to "
6371+
"loss of generic requirements",
6372+
(Type))
6373+
63606374
#define UNDEFINE_DIAGNOSTIC_MACROS
63616375
#include "DefineDiagnosticMacros.h"

include/swift/Sema/CSFix.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,10 @@ enum class FixKind : uint8_t {
386386
/// Ignore a type mismatch while trying to infer generic parameter type
387387
/// from default expression.
388388
IgnoreDefaultExprTypeMismatch,
389+
390+
/// Coerce a result type of a call to a particular existential type
391+
/// by adding `as any <#Type#>`.
392+
AddExplicitExistentialCoercion,
389393
};
390394

391395
class ConstraintFix {
@@ -2925,6 +2929,37 @@ class IgnoreDefaultExprTypeMismatch : public AllowArgumentMismatch {
29252929
}
29262930
};
29272931

2932+
class AddExplicitExistentialCoercion final : public ConstraintFix {
2933+
Type ErasedResultType;
2934+
2935+
AddExplicitExistentialCoercion(ConstraintSystem &cs, Type erasedResultTy,
2936+
ConstraintLocator *locator)
2937+
: ConstraintFix(cs, FixKind::AddExplicitExistentialCoercion, locator),
2938+
ErasedResultType(erasedResultTy) {}
2939+
2940+
public:
2941+
std::string getName() const override {
2942+
return "add explicit existential type coercion";
2943+
}
2944+
2945+
bool diagnose(const Solution &solution, bool asNote = false) const override;
2946+
2947+
static bool
2948+
isRequired(ConstraintSystem &cs, Type resultTy,
2949+
ArrayRef<std::pair<TypeVariableType *, OpenedArchetypeType *>>
2950+
openedExistentials,
2951+
ConstraintLocatorBuilder locator);
2952+
2953+
static bool isRequired(ConstraintSystem &cs, Type resultTy,
2954+
llvm::function_ref<Optional<Type>(TypeVariableType *)>
2955+
findExistentialType,
2956+
ConstraintLocatorBuilder locator);
2957+
2958+
static AddExplicitExistentialCoercion *create(ConstraintSystem &cs,
2959+
Type resultTy,
2960+
ConstraintLocator *locator);
2961+
};
2962+
29282963
} // end namespace constraints
29292964
} // end namespace swift
29302965

lib/Sema/CSDiagnostics.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8104,3 +8104,54 @@ bool DefaultExprTypeMismatch::diagnoseAsError() {
81048104

81058105
return true;
81068106
}
8107+
8108+
bool MissingExplicitExistentialCoercion::diagnoseAsError() {
8109+
auto diagnostic = emitDiagnostic(diag::result_requires_explicit_coercion,
8110+
ErasedResultType);
8111+
fixIt(diagnostic);
8112+
return true;
8113+
}
8114+
8115+
bool MissingExplicitExistentialCoercion::diagnoseAsNote() {
8116+
auto diagnostic = emitDiagnostic(
8117+
diag::candidate_result_requires_explicit_coercion, ErasedResultType);
8118+
fixIt(diagnostic);
8119+
return true;
8120+
}
8121+
8122+
bool MissingExplicitExistentialCoercion::fixItRequiresParens() const {
8123+
auto anchor = getAsExpr(getRawAnchor());
8124+
8125+
// If it's a member reference an an existential metatype, let's
8126+
// use the parent "call" expression.
8127+
if (auto *UDE = dyn_cast_or_null<UnresolvedDotExpr>(anchor))
8128+
anchor = findParentExpr(UDE);
8129+
8130+
if (!anchor)
8131+
return false;
8132+
8133+
const auto &solution = getSolution();
8134+
return llvm::any_of(
8135+
solution.OpenedExistentialTypes,
8136+
[&anchor](const auto &openedExistential) {
8137+
if (auto openedLoc = simplifyLocatorToAnchor(openedExistential.first)) {
8138+
return anchor == getAsExpr(openedLoc);
8139+
}
8140+
return false;
8141+
});
8142+
}
8143+
8144+
void MissingExplicitExistentialCoercion::fixIt(
8145+
InFlightDiagnostic &diagnostic) const {
8146+
bool requiresParens = fixItRequiresParens();
8147+
8148+
auto callRange = getSourceRange();
8149+
8150+
if (requiresParens)
8151+
diagnostic.fixItInsert(callRange.Start, "(");
8152+
8153+
auto printOpts = PrintOptions::forDiagnosticArguments();
8154+
diagnostic.fixItInsertAfter(callRange.End,
8155+
"as " + ErasedResultType->getString(printOpts) +
8156+
(requiresParens ? ")" : ""));
8157+
}

lib/Sema/CSDiagnostics.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2693,6 +2693,53 @@ class DefaultExprTypeMismatch final : public ContextualFailure {
26932693
bool diagnoseAsError() override;
26942694
};
26952695

2696+
/// Diagnose situations where inferring existential type for result of
2697+
/// a call would result in loss of generic requirements.
2698+
///
2699+
/// \code
2700+
/// protocol P {
2701+
/// associatedtype A
2702+
/// }
2703+
///
2704+
/// protocol Q {
2705+
/// associatedtype B: P where B.A == Int
2706+
/// }
2707+
///
2708+
/// func getB<T: Q>(_: T) -> T.B { ... }
2709+
///
2710+
/// func test(v: any Q) {
2711+
/// let _ = getB(v) // <- produces `any P` which looses A == Int
2712+
/// }
2713+
/// \endcode
2714+
class MissingExplicitExistentialCoercion final : public FailureDiagnostic {
2715+
Type ErasedResultType;
2716+
2717+
public:
2718+
MissingExplicitExistentialCoercion(const Solution &solution,
2719+
Type erasedResultTy,
2720+
ConstraintLocator *locator)
2721+
: FailureDiagnostic(solution, locator),
2722+
ErasedResultType(resolveType(erasedResultTy)) {}
2723+
2724+
SourceRange getSourceRange() const override {
2725+
auto rawAnchor = getRawAnchor();
2726+
return {rawAnchor.getStartLoc(), rawAnchor.getEndLoc()};
2727+
}
2728+
2729+
bool diagnoseAsError() override;
2730+
bool diagnoseAsNote() override;
2731+
2732+
private:
2733+
void fixIt(InFlightDiagnostic &diagnostic) const;
2734+
2735+
/// Determine whether the fix-it to add `as any ...` requires parens.
2736+
///
2737+
/// Parens are required to avoid suppressing existential opening
2738+
/// if result of the call is passed as an argument to another call
2739+
/// that requires such opening.
2740+
bool fixItRequiresParens() const;
2741+
};
2742+
26962743
} // end namespace constraints
26972744
} // end namespace swift
26982745

0 commit comments

Comments
 (0)