Skip to content

Commit 3b523d5

Browse files
committed
[TypeChecker] Allow opaque to have multiple underlying types based on availability
Allow opaque type declarations to have multiple unique underlying types if all such types are conditional on availability via `if #available(...)` and there is only one universally available substitution. Neither nesting of availability conditions nor other dynamic conditions are allowed. For example: ```swift protocol P {} @available(iOS 31.337, *) struct X : P { } struct Y : P { } func test() -> some P { if #available(iOS 31.337, *) { // ... some computation ... return X() } return Y() } ``` Allows `some P` to refer to `X` instead of `Y` on `iOS 31.337`.
1 parent a5a50a3 commit 3b523d5

File tree

2 files changed

+329
-67
lines changed

2 files changed

+329
-67
lines changed

lib/Sema/MiscDiagnostics.cpp

Lines changed: 221 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2603,29 +2603,36 @@ class VarDeclUsageChecker : public ASTWalker {
26032603
/// An AST walker that determines the underlying type of an opaque return decl
26042604
/// from its associated function body.
26052605
class OpaqueUnderlyingTypeChecker : public ASTWalker {
2606-
using Candidate = std::pair<Expr *, SubstitutionMap>;
2606+
using Candidate = std::tuple<Expr *, SubstitutionMap, /*isUnique=*/bool>;
2607+
using AvailabilityContext = IfStmt *;
26072608

26082609
ASTContext &Ctx;
26092610
AbstractFunctionDecl *Implementation;
26102611
OpaqueTypeDecl *OpaqueDecl;
26112612
BraceStmt *Body;
2612-
SmallVector<Candidate, 4> Candidates;
2613-
SmallPtrSet<const void *, 4> KnownCandidates;
2613+
2614+
/// A set of all candidates with unique signatures.
2615+
SmallPtrSet<const void *, 4> UniqueSignatures;
2616+
2617+
/// Represents a current availability context. `nullptr` means that
2618+
/// there are no restrictions.
2619+
AvailabilityContext CurrentAvailability = nullptr;
2620+
2621+
/// All of the candidates together with their availability.
2622+
///
2623+
/// If a candidate is found in non-`if #available` context or
2624+
/// `if #available` has other dynamic conditions, it covers 'all'
2625+
/// versions and the context is set to `nullptr`.
2626+
SmallVector<std::pair<AvailabilityContext, Candidate>, 4> Candidates;
26142627

26152628
bool HasInvalidReturn = false;
26162629

26172630
public:
26182631
OpaqueUnderlyingTypeChecker(AbstractFunctionDecl *Implementation,
2619-
OpaqueTypeDecl *OpaqueDecl,
2620-
BraceStmt *Body)
2621-
: Ctx(Implementation->getASTContext()),
2622-
Implementation(Implementation),
2623-
OpaqueDecl(OpaqueDecl),
2624-
Body(Body)
2625-
{
2626-
2627-
}
2628-
2632+
OpaqueTypeDecl *OpaqueDecl, BraceStmt *Body)
2633+
: Ctx(Implementation->getASTContext()), Implementation(Implementation),
2634+
OpaqueDecl(OpaqueDecl), Body(Body) {}
2635+
26292636
void check() {
26302637
Body->walk(*this);
26312638

@@ -2642,95 +2649,242 @@ class OpaqueUnderlyingTypeChecker : public ASTWalker {
26422649
return;
26432650
}
26442651

2652+
if (Candidates.size() == 1) {
2653+
finalizeUnique(Candidates.front().second);
2654+
return;
2655+
}
2656+
26452657
// Check whether all of the underlying type candidates match up.
26462658
// TODO [OPAQUE SUPPORT]: diagnose multiple opaque types
2647-
SubstitutionMap underlyingSubs = Candidates.front().second;
2648-
if (Candidates.size() > 1) {
2649-
Optional<std::pair<unsigned, GenericTypeParamType *>> mismatch;
2650-
2651-
auto opaqueParams = OpaqueDecl->getOpaqueGenericParams();
2652-
for (auto index : indices(opaqueParams)) {
2653-
auto *genericParam = opaqueParams[index];
2654-
2655-
Type underlyingType = Type(genericParam).subst(underlyingSubs);
2656-
bool found = false;
2657-
for (const auto &candidate : Candidates) {
2658-
Type otherType = Type(genericParam).subst(candidate.second);
2659-
2660-
if (!underlyingType->isEqual(otherType)) {
2661-
mismatch.emplace(index, genericParam);
2662-
found = true;
2663-
break;
2664-
}
2665-
}
26662659

2667-
if (found)
2660+
// There is a single unique signature, which means that all returns
2661+
// matched.
2662+
if (llvm::count_if(Candidates, [](const auto &entry) {
2663+
const auto &candidate = entry.second;
2664+
return std::get<2>(candidate); // isUnique field.
2665+
}) == 1) {
2666+
finalizeUnique(Candidates.front().second);
2667+
return;
2668+
}
2669+
2670+
SmallVector<Candidate, 4> universalyUniqueCandidates;
2671+
2672+
for (const auto &entry : Candidates) {
2673+
AvailabilityContext availability = entry.first;
2674+
const auto &candidate = entry.second;
2675+
2676+
// Unique candidate without availability context.
2677+
if (!availability && std::get<2>(candidate))
2678+
universalyUniqueCandidates.push_back(candidate);
2679+
}
2680+
2681+
// TODO(diagnostics): Need a tailored diagnostic for this case.
2682+
if (universalyUniqueCandidates.empty()) {
2683+
Implementation->diagnose(diag::opaque_type_no_underlying_type_candidates);
2684+
return;
2685+
}
2686+
2687+
// If there is a single universally available unique candidate
2688+
// the underlying type would have to be determined at runtime
2689+
// based on the results of availability checks.
2690+
if (universalyUniqueCandidates.size() == 1) {
2691+
finalizeOpaque(universalyUniqueCandidates.front());
2692+
return;
2693+
}
2694+
2695+
// A list of all mismatches discovered across all candidates.
2696+
// If there are any mismatches in availability contexts, they
2697+
// are not diagnosed but propagated to the declaration.
2698+
Optional<std::pair<unsigned, GenericTypeParamType *>> mismatch;
2699+
2700+
auto opaqueParams = OpaqueDecl->getOpaqueGenericParams();
2701+
SubstitutionMap underlyingSubs = std::get<1>(Candidates.front().second);
2702+
2703+
for (auto index : indices(opaqueParams)) {
2704+
auto *genericParam = opaqueParams[index];
2705+
2706+
Type underlyingType = Type(genericParam).subst(underlyingSubs);
2707+
bool found = false;
2708+
for (const auto &candidate : universalyUniqueCandidates) {
2709+
Type otherType = Type(genericParam).subst(std::get<1>(candidate));
2710+
2711+
if (!underlyingType->isEqual(otherType)) {
2712+
mismatch.emplace(index, genericParam);
2713+
found = true;
26682714
break;
2715+
}
26692716
}
2670-
assert(mismatch.hasValue());
26712717

2672-
if (auto genericParam =
2673-
OpaqueDecl->getExplicitGenericParam(mismatch->first)) {
2674-
Implementation->diagnose(
2675-
diag::opaque_type_mismatched_underlying_type_candidates_named,
2676-
genericParam->getName())
2718+
if (found)
2719+
break;
2720+
}
2721+
2722+
assert(mismatch.hasValue());
2723+
2724+
if (auto genericParam =
2725+
OpaqueDecl->getExplicitGenericParam(mismatch->first)) {
2726+
Implementation
2727+
->diagnose(
2728+
diag::opaque_type_mismatched_underlying_type_candidates_named,
2729+
genericParam->getName())
26772730
.highlight(genericParam->getLoc());
2678-
} else {
2679-
TypeRepr *opaqueRepr =
2680-
OpaqueDecl->getOpaqueReturnTypeReprs()[mismatch->first];
2681-
Implementation->diagnose(
2682-
diag::opaque_type_mismatched_underlying_type_candidates,
2683-
opaqueRepr)
2731+
} else {
2732+
TypeRepr *opaqueRepr =
2733+
OpaqueDecl->getOpaqueReturnTypeReprs()[mismatch->first];
2734+
Implementation
2735+
->diagnose(diag::opaque_type_mismatched_underlying_type_candidates,
2736+
opaqueRepr)
26842737
.highlight(opaqueRepr->getSourceRange());
2685-
}
2738+
}
26862739

2687-
for (auto candidate : Candidates) {
2688-
Ctx.Diags.diagnose(candidate.first->getLoc(),
2689-
diag::opaque_type_underlying_type_candidate_here,
2690-
Type(mismatch->second).subst(candidate.second));
2691-
}
2692-
return;
2740+
for (const auto &candidate : universalyUniqueCandidates) {
2741+
Ctx.Diags.diagnose(std::get<0>(candidate)->getLoc(),
2742+
diag::opaque_type_underlying_type_candidate_here,
2743+
Type(mismatch->second).subst(std::get<1>(candidate)));
26932744
}
2694-
2745+
}
2746+
2747+
bool isSelfReferencing(const Candidate &candidate) {
2748+
auto substitutions = std::get<1>(candidate);
2749+
26952750
// The underlying type can't be defined recursively
26962751
// in terms of the opaque type itself.
26972752
auto opaqueTypeInContext = Implementation->mapTypeIntoContext(
26982753
OpaqueDecl->getDeclaredInterfaceType());
26992754
for (auto genericParam : OpaqueDecl->getOpaqueGenericParams()) {
2700-
auto underlyingType = Type(genericParam).subst(underlyingSubs);
2701-
auto isSelfReferencing = underlyingType.findIf([&](Type t) -> bool {
2702-
return t->isEqual(opaqueTypeInContext);
2703-
});
2755+
auto underlyingType = Type(genericParam).subst(substitutions);
2756+
auto isSelfReferencing = underlyingType.findIf(
2757+
[&](Type t) -> bool { return t->isEqual(opaqueTypeInContext); });
27042758

27052759
if (isSelfReferencing) {
2706-
unsigned index = genericParam->getIndex();
2707-
Ctx.Diags.diagnose(Candidates.front().first->getLoc(),
2760+
Ctx.Diags.diagnose(std::get<0>(candidate)->getLoc(),
27082761
diag::opaque_type_self_referential_underlying_type,
2709-
underlyingSubs.getReplacementTypes()[index]);
2710-
return;
2762+
underlyingType);
2763+
return true;
27112764
}
27122765
}
27132766

2767+
return false;
2768+
}
2769+
2770+
// A single unique underlying substitution.
2771+
void finalizeUnique(const Candidate &candidate) {
27142772
// If we have one successful candidate, then save it as the underlying
27152773
// substitutions of the opaque decl.
27162774
OpaqueDecl->setUniqueUnderlyingTypeSubstitutions(
2717-
underlyingSubs.mapReplacementTypesOutOfContext());
2775+
std::get<1>(candidate).mapReplacementTypesOutOfContext());
27182776
}
2719-
2777+
2778+
// There is no clear winner here since there are candidates within
2779+
// limited availability contexts.
2780+
void finalizeOpaque(const Candidate &universallyAvailable) {
2781+
SmallVector<OpaqueTypeDecl::ConditionallyAvailableSubstitutions *, 4>
2782+
conditionalSubstitutions;
2783+
2784+
for (const auto &entry : Candidates) {
2785+
auto availabilityContext = entry.first;
2786+
const auto &candidate = entry.second;
2787+
2788+
if (!availabilityContext)
2789+
continue;
2790+
2791+
SmallVector<VersionRange, 4> conditions;
2792+
2793+
llvm::transform(availabilityContext->getCond(),
2794+
std::back_inserter(conditions),
2795+
[&](const StmtConditionElement &elt) {
2796+
return elt.getAvailability()->getAvailableRange();
2797+
});
2798+
2799+
conditionalSubstitutions.push_back(
2800+
OpaqueTypeDecl::ConditionallyAvailableSubstitutions::get(
2801+
Ctx, conditions,
2802+
std::get<1>(candidate).mapReplacementTypesOutOfContext()));
2803+
}
2804+
2805+
// Add universally available choice as the last one.
2806+
conditionalSubstitutions.push_back(
2807+
OpaqueTypeDecl::ConditionallyAvailableSubstitutions::get(
2808+
Ctx, {VersionRange::empty()},
2809+
std::get<1>(universallyAvailable)
2810+
.mapReplacementTypesOutOfContext()));
2811+
2812+
OpaqueDecl->setConditionallyAvailableSubstitutions(
2813+
conditionalSubstitutions);
2814+
}
2815+
27202816
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
27212817
if (auto underlyingToOpaque = dyn_cast<UnderlyingToOpaqueExpr>(E)) {
27222818
auto key =
27232819
underlyingToOpaque->substitutions.getCanonical().getOpaqueValue();
2724-
if (KnownCandidates.insert(key).second) {
2725-
Candidates.push_back(std::make_pair(underlyingToOpaque->getSubExpr(),
2726-
underlyingToOpaque->substitutions));
2820+
2821+
auto isUnique = UniqueSignatures.insert(key).second;
2822+
2823+
auto candidate =
2824+
std::make_tuple(underlyingToOpaque->getSubExpr(),
2825+
underlyingToOpaque->substitutions, isUnique);
2826+
2827+
if (isSelfReferencing(candidate)) {
2828+
HasInvalidReturn = true;
2829+
return {false, nullptr};
27272830
}
2831+
2832+
Candidates.push_back({CurrentAvailability, candidate});
27282833
return {false, E};
27292834
}
2835+
27302836
return {true, E};
27312837
}
27322838

27332839
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
2840+
if (auto *If = dyn_cast<IfStmt>(S)) {
2841+
if (Parent.getAsStmt() != Body) {
2842+
// If this is not a top-level `if`, let's drop
2843+
// contextual information that has been set previously.
2844+
CurrentAvailability = nullptr;
2845+
return {true, S};
2846+
}
2847+
2848+
// If this is `if #available` statement with no other dynamic
2849+
// conditions, let's check if it returns opaque type directly.
2850+
if (llvm::all_of(If->getCond(), [&](const auto &condition) {
2851+
return condition.getKind() == StmtConditionElement::CK_Availability;
2852+
})) {
2853+
// Check return statement directly with availability context set.
2854+
if (auto *Then = dyn_cast<BraceStmt>(If->getThenStmt())) {
2855+
llvm::SaveAndRestore<ParentTy> parent(Parent, Then);
2856+
2857+
for (auto element : Then->getElements()) {
2858+
auto *Return = getAsStmt<ReturnStmt>(element);
2859+
2860+
// If this is not a direct return statement, walk into it
2861+
// without setting contextual availability because we want
2862+
// to find all `return`s.
2863+
if (!(Return && Return->hasResult())) {
2864+
element.walk(*this);
2865+
continue;
2866+
}
2867+
2868+
// Note that we are about to walk into a return statement
2869+
// that is located in a `if #available` without any other
2870+
// conditions.
2871+
llvm::SaveAndRestore<AvailabilityContext> context(
2872+
CurrentAvailability, If);
2873+
2874+
Return->getResult()->walk(*this);
2875+
}
2876+
}
2877+
2878+
// Walk the else branch directly as well.
2879+
if (auto *Else = If->getElseStmt()) {
2880+
llvm::SaveAndRestore<ParentTy> parent(Parent, If);
2881+
Else->walk(*this);
2882+
}
2883+
2884+
return {false, S};
2885+
}
2886+
}
2887+
27342888
if (auto *RS = dyn_cast<ReturnStmt>(S)) {
27352889
if (RS->hasResult()) {
27362890
auto resultTy = RS->getResult()->getType();
@@ -2743,7 +2897,7 @@ class OpaqueUnderlyingTypeChecker : public ASTWalker {
27432897

27442898
return {true, S};
27452899
}
2746-
2900+
27472901
// Don't descend into nested decls.
27482902
bool walkToDeclPre(Decl *D) override {
27492903
return false;

0 commit comments

Comments
 (0)