Skip to content

Commit b9b5bd2

Browse files
authored
Merge pull request swiftlang#34523 from xedin/static-member-lookup-on-protocol
[SE-0299][TypeChecker] Allow static member references on protocol metatypes in generic contexts
2 parents 60ae226 + c4ee329 commit b9b5bd2

18 files changed

+801
-48
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1902,6 +1902,12 @@ ERROR(type_does_not_conform_owner,none,
19021902
ERROR(type_does_not_conform_in_decl_ref,none,
19031903
"referencing %0 %1 on %2 requires that %3 conform to %4",
19041904
(DescriptiveDeclKind, DeclName, Type, Type, Type))
1905+
ERROR(contextual_member_ref_on_protocol_requires_self_requirement,none,
1906+
"contextual member reference to %0 %1 requires "
1907+
"'Self' constraint in the protocol extension",
1908+
(DescriptiveDeclKind, DeclName))
1909+
NOTE(missing_sametype_requirement_on_self,none,
1910+
"missing same-type requirement on 'Self'", ())
19051911
ERROR(type_does_not_conform_anyobject_in_decl_ref,none,
19061912
"referencing %0 %1 on %2 requires that %3 be a class type",
19071913
(DescriptiveDeclKind, DeclName, Type, Type, Type))

include/swift/Sema/CSBindings.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,20 @@ struct PotentialBindings {
276276

277277
/// Determine whether the set of bindings is non-empty.
278278
explicit operator bool() const {
279+
return hasViableBindings()|| isDirectHole();
280+
}
281+
282+
/// Determine whether this set has any "viable" (or non-hole) bindings.
283+
///
284+
/// A viable binding could be - a direct or transitive binding
285+
/// inferred from a constraint, literal binding, or defaltable
286+
/// binding.
287+
///
288+
/// A hole is not considered a viable binding since it doesn't
289+
/// add any new type information to constraint system.
290+
bool hasViableBindings() const {
279291
return !Bindings.empty() || getNumViableLiteralBindings() > 0 ||
280-
!Defaults.empty() || isDirectHole();
292+
!Defaults.empty();
281293
}
282294

283295
/// Determines whether this type variable could be `nil`,

include/swift/Sema/CSFix.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,11 @@ enum class FixKind : uint8_t {
307307
/// convertible, but runtime does not support such convertions. e.g.
308308
/// function type casts.
309309
AllowUnsupportedRuntimeCheckedCast,
310+
311+
/// Allow reference to a static member on a protocol metatype
312+
/// even though result type of the reference doesn't conform
313+
/// to an expected protocol.
314+
AllowInvalidStaticMemberRefOnProtocolMetatype,
310315
};
311316

312317
class ConstraintFix {
@@ -2307,6 +2312,29 @@ class AllowUnsupportedRuntimeCheckedCast final
23072312
CheckedCastKind kind, ConstraintLocator *locator);
23082313
};
23092314

2315+
class AllowInvalidStaticMemberRefOnProtocolMetatype final
2316+
: public ConstraintFix {
2317+
AllowInvalidStaticMemberRefOnProtocolMetatype(ConstraintSystem &cs,
2318+
ConstraintLocator *locator)
2319+
: ConstraintFix(cs,
2320+
FixKind::AllowInvalidStaticMemberRefOnProtocolMetatype,
2321+
locator) {}
2322+
2323+
public:
2324+
std::string getName() const override {
2325+
return "allow invalid static member reference on a protocol metatype";
2326+
}
2327+
2328+
bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
2329+
return diagnose(*commonFixes.front().first);
2330+
}
2331+
2332+
bool diagnose(const Solution &solution, bool asNote = false) const override;
2333+
2334+
static AllowInvalidStaticMemberRefOnProtocolMetatype *
2335+
create(ConstraintSystem &cs, ConstraintLocator *locator);
2336+
};
2337+
23102338
} // end namespace constraints
23112339
} // end namespace swift
23122340

include/swift/Sema/Constraint.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,16 @@ enum class ConstraintKind : char {
177177
/// - Handled specially by binding inference, specifically contributes
178178
/// to the bindings only if there are no contextual types available.
179179
DefaultClosureType,
180+
/// The first type represents a result of an unresolved member chain,
181+
/// and the second type is its base type. This constraint acts almost
182+
/// like `Equal` but also enforces following semantics:
183+
///
184+
/// - It's possible to infer a base from a result type by looking through
185+
/// this constraint, but it's only solved when both types are bound.
186+
///
187+
/// - If base is a protocol metatype, this constraint becomes a conformance
188+
/// check instead of an equality.
189+
UnresolvedMemberChainBase,
180190
};
181191

182192
/// Classification of the different kinds of constraints.
@@ -570,6 +580,7 @@ class Constraint final : public llvm::ilist_node<Constraint>,
570580
case ConstraintKind::OneWayEqual:
571581
case ConstraintKind::OneWayBindParam:
572582
case ConstraintKind::DefaultClosureType:
583+
case ConstraintKind::UnresolvedMemberChainBase:
573584
return ConstraintClassification::Relational;
574585

575586
case ConstraintKind::ValueMember:

include/swift/Sema/ConstraintSystem.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1462,7 +1462,11 @@ struct MemberLookupResult {
14621462
UR_ReferenceWritableKeyPathOnMutatingMember,
14631463

14641464
/// This is a KeyPath whose root type is AnyObject
1465-
UR_KeyPathWithAnyObjectRootType
1465+
UR_KeyPathWithAnyObjectRootType,
1466+
1467+
/// This is a static member being access through a protocol metatype
1468+
/// but its result type doesn't conform to this protocol.
1469+
UR_InvalidStaticMemberOnProtocolMetatype,
14661470
};
14671471

14681472
/// This is a list of considered (but rejected) candidates, along with a
@@ -4570,6 +4574,12 @@ class ConstraintSystem {
45704574
TypeMatchOptions flags,
45714575
ConstraintLocatorBuilder locator);
45724576

4577+
/// Simplify an equality constraint between result and base types of
4578+
/// an unresolved member chain.
4579+
SolutionKind simplifyUnresolvedMemberChainBaseConstraint(
4580+
Type first, Type second, TypeMatchOptions flags,
4581+
ConstraintLocatorBuilder locator);
4582+
45734583
/// Simplify a conversion constraint by applying the given
45744584
/// reduction rule, which is known to apply at the outermost level.
45754585
SolutionKind simplifyRestrictedConstraintImpl(
@@ -5531,6 +5541,11 @@ bool hasExplicitResult(ClosureExpr *closure);
55315541
void performSyntacticDiagnosticsForTarget(
55325542
const SolutionApplicationTarget &target, bool isExprStmt);
55335543

5544+
/// Given a member of a protocol, check whether `Self` type of that
5545+
/// protocol is contextually bound to some concrete type via same-type
5546+
/// generic requirement and if so return that type or null type otherwise.
5547+
Type getConcreteReplacementForProtocolSelfType(ValueDecl *member);
5548+
55345549
} // end namespace constraints
55355550

55365551
template<typename ...Args>

lib/Sema/CSApply.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,38 @@ namespace {
11661166
baseIsInstance = false;
11671167
isExistentialMetatype = baseMeta->is<ExistentialMetatypeType>();
11681168
baseTy = baseMeta->getInstanceType();
1169+
1170+
// A valid reference to a static member (computed property or a method)
1171+
// declared on a protocol is only possible if result type conforms to
1172+
// that protocol, otherwise it would be impossible to find a witness to
1173+
// use.
1174+
// Such means that (for valid references) base expression here could be
1175+
// adjusted to point to a type conforming to a protocol as-if reference
1176+
// has originated directly from it e.g.
1177+
//
1178+
// \code
1179+
// protocol P {}
1180+
// struct S : P {}
1181+
//
1182+
// extension P {
1183+
// static var foo: S { S() }
1184+
// }
1185+
//
1186+
// _ = P.foo
1187+
// \endcode
1188+
//
1189+
// Here `P.foo` would be replaced with `S.foo`
1190+
if (!isExistentialMetatype && baseTy->is<ProtocolType>() &&
1191+
member->isStatic()) {
1192+
auto selfParam =
1193+
overload.openedFullType->castTo<FunctionType>()->getParams()[0];
1194+
1195+
Type baseTy =
1196+
simplifyType(selfParam.getPlainType())->getMetatypeInstanceType();
1197+
1198+
base = TypeExpr::createImplicitHack(base->getLoc(), baseTy, context);
1199+
cs.cacheType(base);
1200+
}
11691201
}
11701202

11711203
// Build a member reference.

lib/Sema/CSBindings.cpp

Lines changed: 109 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,33 @@ bool PotentialBinding::isViableForJoin() const {
5757
}
5858

5959
bool PotentialBindings::isDelayed() const {
60+
if (auto *locator = TypeVar->getImpl().getLocator()) {
61+
if (locator->isLastElement<LocatorPathElt::MemberRefBase>()) {
62+
// If first binding is a "fallback" to a protocol type,
63+
// it means that this type variable should be delayed
64+
// until it either gains more contextual information, or
65+
// there are no other type variables to attempt to make
66+
// forward progress.
67+
if (Bindings.empty())
68+
return true;
69+
70+
71+
if (Bindings[0].BindingType->is<ProtocolType>())
72+
return true;
73+
}
74+
75+
// Since force unwrap preserves l-valueness, resulting
76+
// type variable has to be delayed until either l-value
77+
// binding becomes available or there are no other
78+
// variables to attempt.
79+
if (locator->directlyAt<ForceValueExpr>() &&
80+
TypeVar->getImpl().canBindToLValue()) {
81+
return llvm::none_of(Bindings, [](const PotentialBinding &binding) {
82+
return binding.BindingType->is<LValueType>();
83+
});
84+
}
85+
}
86+
6087
if (isHole()) {
6188
auto *locator = TypeVar->getImpl().getLocator();
6289
assert(locator && "a hole without locator?");
@@ -98,19 +125,6 @@ bool PotentialBindings::isDelayed() const {
98125
});
99126
}
100127

101-
if (auto *locator = TypeVar->getImpl().getLocator()) {
102-
// Since force unwrap preserves l-valueness, resulting
103-
// type variable has to be delayed until either l-value
104-
// binding becomes available or there are no other
105-
// variables to attempt.
106-
if (locator->directlyAt<ForceValueExpr>() &&
107-
TypeVar->getImpl().canBindToLValue()) {
108-
return llvm::none_of(Bindings, [](const PotentialBinding &binding) {
109-
return binding.BindingType->is<LValueType>();
110-
});
111-
}
112-
}
113-
114128
return !DelayedBy.empty();
115129
}
116130

@@ -140,6 +154,25 @@ bool PotentialBindings::isPotentiallyIncomplete() const {
140154
if (!locator)
141155
return false;
142156

157+
if (locator->isLastElement<LocatorPathElt::MemberRefBase>() &&
158+
!Bindings.empty()) {
159+
// If the base of the unresolved member reference like `.foo`
160+
// couldn't be resolved we'd want to bind it to a hole at the
161+
// very last moment possible, just like generic parameters.
162+
if (isHole())
163+
return true;
164+
165+
auto &binding = Bindings.front();
166+
// If base type of a member chain is inferred to be a protocol type,
167+
// let's consider this binding set to be potentially incomplete since
168+
// that's done as a last resort effort at resolving first member.
169+
if (auto *constraint = binding.getSource()) {
170+
if (binding.BindingType->is<ProtocolType>() &&
171+
constraint->getKind() == ConstraintKind::ConformsTo)
172+
return true;
173+
}
174+
}
175+
143176
if (locator->isLastElement<LocatorPathElt::UnresolvedMemberChainResult>()) {
144177
// If subtyping is allowed and this is a result of an implicit member chain,
145178
// let's delay binding it to an optional until its object type resolved too or
@@ -159,12 +192,6 @@ bool PotentialBindings::isPotentiallyIncomplete() const {
159192
}
160193

161194
if (isHole()) {
162-
// If the base of the unresolved member reference like `.foo`
163-
// couldn't be resolved we'd want to bind it to a hole at the
164-
// very last moment possible, just like generic parameters.
165-
if (locator->isLastElement<LocatorPathElt::MemberRefBase>())
166-
return true;
167-
168195
// Delay resolution of the code completion expression until
169196
// the very end to give it a chance to be bound to some
170197
// contextual type even if it's a hole.
@@ -408,6 +435,50 @@ void PotentialBindings::finalize(
408435
&inferredBindings) {
409436
inferTransitiveProtocolRequirements(inferredBindings);
410437
inferTransitiveBindings(inferredBindings);
438+
439+
if (auto *locator = TypeVar->getImpl().getLocator()) {
440+
if (locator->isLastElement<LocatorPathElt::MemberRefBase>()) {
441+
// If this is a base of an unresolved member chain, as a last
442+
// resort effort let's infer base to be a protocol type based
443+
// on contextual conformance requirements.
444+
//
445+
// This allows us to find solutions in cases like this:
446+
//
447+
// \code
448+
// func foo<T: P>(_: T) {}
449+
// foo(.bar) <- `.bar` should be a static member of `P`.
450+
// \endcode
451+
if (!hasViableBindings() && TransitiveProtocols.hasValue()) {
452+
for (auto *constraint : *TransitiveProtocols) {
453+
auto protocolTy = constraint->getSecondType();
454+
addPotentialBinding(
455+
{protocolTy, AllowedBindingKind::Exact, constraint});
456+
}
457+
}
458+
}
459+
460+
if (CS.shouldAttemptFixes() &&
461+
locator->isLastElement<LocatorPathElt::UnresolvedMemberChainResult>()) {
462+
// Let's see whether this chain is valid, if it isn't then to avoid
463+
// diagnosing the same issue multiple different ways, let's infer
464+
// result of the chain to be a hole.
465+
auto *resultExpr =
466+
castToExpr<UnresolvedMemberChainResultExpr>(locator->getAnchor());
467+
auto *baseLocator = CS.getConstraintLocator(
468+
resultExpr->getChainBase(), ConstraintLocator::UnresolvedMember);
469+
470+
if (CS.hasFixFor(
471+
baseLocator,
472+
FixKind::AllowInvalidStaticMemberRefOnProtocolMetatype)) {
473+
CS.recordPotentialHole(TypeVar);
474+
// Clear all of the previously collected bindings which are inferred
475+
// from inside of a member chain.
476+
Bindings.remove_if([](const PotentialBinding &binding) {
477+
return binding.Kind == AllowedBindingKind::Supertypes;
478+
});
479+
}
480+
}
481+
}
411482
}
412483

413484
PotentialBindings::BindingScore
@@ -451,6 +522,14 @@ Optional<PotentialBindings> ConstraintSystem::determineBestBindings() {
451522
auto isViableForRanking = [this](const PotentialBindings &bindings) -> bool {
452523
auto *typeVar = bindings.TypeVar;
453524

525+
// Type variable representing a base of unresolved member chain should
526+
// always be considered viable for ranking since it's allow to infer
527+
// types from transitive protocol requirements.
528+
if (auto *locator = typeVar->getImpl().getLocator()) {
529+
if (locator->isLastElement<LocatorPathElt::MemberRefBase>())
530+
return true;
531+
}
532+
454533
// If type variable is marked as a potential hole there is always going
455534
// to be at least one binding available for it.
456535
if (shouldAttemptFixes() && typeVar->getImpl().canBindToHole())
@@ -967,6 +1046,13 @@ PotentialBindings::inferFromRelational(Constraint *constraint) {
9671046
if (!BGT || !isKnownKeyPathDecl(CS.getASTContext(), BGT->getDecl()))
9681047
return None;
9691048
}
1049+
1050+
// Don't allow a protocol type to get propagated from the base to the result
1051+
// type of a chain, Result should always be a concrete type which conforms
1052+
// to the protocol inferred for the base.
1053+
if (constraint->getKind() == ConstraintKind::UnresolvedMemberChainBase &&
1054+
kind == AllowedBindingKind::Subtypes && type->is<ProtocolType>())
1055+
return None;
9701056
}
9711057

9721058
// If the source of the binding is 'OptionalObject' constraint
@@ -1063,7 +1149,8 @@ PotentialBindings::inferFromRelational(Constraint *constraint) {
10631149

10641150
case ConstraintKind::Bind:
10651151
case ConstraintKind::BindParam:
1066-
case ConstraintKind::Equal: {
1152+
case ConstraintKind::Equal:
1153+
case ConstraintKind::UnresolvedMemberChainBase: {
10671154
EquivalentTo.insert({bindingTypeVar, constraint});
10681155
break;
10691156
}
@@ -1118,7 +1205,8 @@ void PotentialBindings::infer(Constraint *constraint) {
11181205
case ConstraintKind::Conversion:
11191206
case ConstraintKind::ArgumentConversion:
11201207
case ConstraintKind::OperatorArgumentConversion:
1121-
case ConstraintKind::OptionalObject: {
1208+
case ConstraintKind::OptionalObject:
1209+
case ConstraintKind::UnresolvedMemberChainBase: {
11221210
auto binding = inferFromRelational(constraint);
11231211
if (!binding)
11241212
break;

0 commit comments

Comments
 (0)