Sema: Fix crash with unresolved dot expression with optional protocol metatype base

Mostly fixes <rdar://problem/35945827>, but there is a case that should
work that does not type check. At least we don't crash though.
This commit is contained in:
Slava Pestov
2017-12-12 15:30:53 -08:00
parent e50d4bbec7
commit d843b10bcc
3 changed files with 102 additions and 76 deletions

View File

@@ -1204,7 +1204,16 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy,
// to replace the metatype with 'Self' // to replace the metatype with 'Self'
// error saying the lookup cannot be on a protocol metatype // error saying the lookup cannot be on a protocol metatype
if (auto metatypeTy = baseObjTy->getAs<MetatypeType>()) { if (auto metatypeTy = baseObjTy->getAs<MetatypeType>()) {
assert(metatypeTy->getInstanceType()->isExistentialType()); auto instanceTy = metatypeTy->getInstanceType();
// This will only happen if we have an unresolved dot expression
// (.foo) where foo is a protocol member and the contextual type is
// an optional protocol metatype.
if (auto objectTy = instanceTy->getAnyOptionalObjectType()) {
instanceTy = objectTy;
baseObjTy = MetatypeType::get(objectTy);
}
assert(instanceTy->isExistentialType());
// Give a customized message if we're accessing a member type // Give a customized message if we're accessing a member type
// of a protocol -- otherwise a diagnostic talking about // of a protocol -- otherwise a diagnostic talking about
@@ -1220,7 +1229,7 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy,
} else if (isa<ConstructorDecl>(member)) { } else if (isa<ConstructorDecl>(member)) {
Diag.emplace(diagnose(loc, Diag.emplace(diagnose(loc,
diag::construct_protocol_by_name, diag::construct_protocol_by_name,
metatypeTy->getInstanceType())); instanceTy));
} else { } else {
Diag.emplace(diagnose(loc, Diag.emplace(diagnose(loc,
diag::could_not_use_type_member_on_protocol_metatype, diag::could_not_use_type_member_on_protocol_metatype,
@@ -1234,8 +1243,7 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy,
// If we are in a protocol extension of 'Proto' and we see // If we are in a protocol extension of 'Proto' and we see
// 'Proto.static', suggest 'Self.static' // 'Proto.static', suggest 'Self.static'
if (auto extensionContext = parent->getAsProtocolExtensionContext()) { if (auto extensionContext = parent->getAsProtocolExtensionContext()) {
if (extensionContext->getDeclaredType()->isEqual( if (extensionContext->getDeclaredType()->isEqual(instanceTy)) {
metatypeTy->getInstanceType())) {
Diag->fixItReplace(baseRange, "Self"); Diag->fixItReplace(baseRange, "Self");
} }
} }

View File

@@ -2899,57 +2899,12 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
ConstraintLocator *memberLocator, ConstraintLocator *memberLocator,
bool includeInaccessibleMembers) { bool includeInaccessibleMembers) {
Type baseObjTy = baseTy->getRValueType(); Type baseObjTy = baseTy->getRValueType();
// Dig out the instance type and figure out what members of the instance type
// we are going to see.
bool isMetatype = false;
bool isModule = false;
bool hasInstanceMembers = false;
bool hasInstanceMethods = false;
bool hasStaticMembers = false;
Type instanceTy = baseObjTy; Type instanceTy = baseObjTy;
if (baseObjTy->is<ModuleType>()) {
hasStaticMembers = true;
isModule = true;
} else if (auto baseObjMeta = baseObjTy->getAs<AnyMetatypeType>()) {
instanceTy = baseObjMeta->getInstanceType();
isMetatype = true;
if (baseObjMeta->is<ExistentialMetatypeType>()) {
// An instance of an existential metatype is a concrete type conforming
// to the existential, say Self. Instance members of the concrete type
// have type Self -> T -> U, but we don't know what Self is at compile
// time so we cannot refer to them. Static methods are fine, on the other
// hand -- we already know that they do not have Self or associated type
// requirements, since otherwise we would not be able to refer to the
// existential metatype in the first place.
hasStaticMembers = true;
} else if (instanceTy->isExistentialType()) {
// A protocol metatype has instance methods with type P -> T -> U, but
// not instance properties or static members -- the metatype value itself
// doesn't give us a witness so there's no static method to bind.
hasInstanceMethods = true;
} else {
// Metatypes of nominal types and archetypes have instance methods and
// static members, but not instance properties.
// FIXME: partial application of properties
hasInstanceMethods = true;
hasStaticMembers = true;
}
// If we're at the root of an unevaluated context, we can if (auto baseObjMeta = baseObjTy->getAs<AnyMetatypeType>()) {
// reference instance members on the metatype. instanceTy = baseObjMeta->getInstanceType();
if (memberLocator &&
UnevaluatedRootExprs.count(memberLocator->getAnchor())) {
hasInstanceMembers = true;
}
} else {
// Otherwise, we can access all instance members.
hasInstanceMembers = true;
hasInstanceMethods = true;
} }
bool isExistential = instanceTy->isExistentialType();
if (instanceTy->isTypeVariableOrMember() || if (instanceTy->isTypeVariableOrMember() ||
instanceTy->is<UnresolvedType>()) { instanceTy->is<UnresolvedType>()) {
MemberLookupResult result; MemberLookupResult result;
@@ -3020,7 +2975,8 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
/// Determine whether the given declaration has compatible argument /// Determine whether the given declaration has compatible argument
/// labels. /// labels.
auto hasCompatibleArgumentLabels = [&](ValueDecl *decl) -> bool { auto hasCompatibleArgumentLabels = [&argumentLabels](Type baseObjTy,
ValueDecl *decl) -> bool {
if (!argumentLabels) if (!argumentLabels)
return true; return true;
@@ -3029,9 +2985,9 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
// the member lookup binding the first level. But there are cases where // the member lookup binding the first level. But there are cases where
// we can get an unapplied declaration reference back. // we can get an unapplied declaration reference back.
unsigned parameterDepth; unsigned parameterDepth;
if (isModule) { if (baseObjTy->is<ModuleType>()) {
parameterDepth = 0; parameterDepth = 0;
} else if (isMetatype && decl->isInstanceMember()) { } else if (baseObjTy->is<AnyMetatypeType>() && decl->isInstanceMember()) {
parameterDepth = 0; parameterDepth = 0;
} else { } else {
parameterDepth = 1; parameterDepth = 1;
@@ -3060,18 +3016,12 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
} }
} }
// The set of directly accessible types, which is only used when
// we're performing dynamic lookup into an existential type.
bool isDynamicLookup = instanceTy->isAnyObject();
// If the instance type is String bridged to NSString, compute // If the instance type is String bridged to NSString, compute
// the type we'll look in for bridging. // the type we'll look in for bridging.
Type bridgedClass;
Type bridgedType; Type bridgedType;
if (instanceTy->getAnyNominal() == TC.Context.getStringDecl()) { if (baseObjTy->getAnyNominal() == TC.Context.getStringDecl()) {
if (Type classType = TC.Context.getBridgedToObjC(DC, instanceTy)) { if (Type classType = TC.Context.getBridgedToObjC(DC, instanceTy)) {
bridgedClass = classType; bridgedType = classType;
bridgedType = isMetatype ? MetatypeType::get(classType) : classType;
} }
} }
bool labelMismatch = false; bool labelMismatch = false;
@@ -3092,9 +3042,56 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
if (!decl->hasInterfaceType()) if (!decl->hasInterfaceType())
return; return;
// Dig out the instance type and figure out what members of the instance type
// we are going to see.
auto baseTy = candidate.getBaseType();
auto baseObjTy = baseTy->getRValueType();
bool hasInstanceMembers = false;
bool hasInstanceMethods = false;
bool hasStaticMembers = false;
Type instanceTy = baseObjTy;
if (baseObjTy->is<ModuleType>()) {
hasStaticMembers = true;
} else if (auto baseObjMeta = baseObjTy->getAs<AnyMetatypeType>()) {
instanceTy = baseObjMeta->getInstanceType();
if (baseObjMeta->is<ExistentialMetatypeType>()) {
// An instance of an existential metatype is a concrete type conforming
// to the existential, say Self. Instance members of the concrete type
// have type Self -> T -> U, but we don't know what Self is at compile
// time so we cannot refer to them. Static methods are fine, on the other
// hand -- we already know that they do not have Self or associated type
// requirements, since otherwise we would not be able to refer to the
// existential metatype in the first place.
hasStaticMembers = true;
} else if (instanceTy->isExistentialType()) {
// A protocol metatype has instance methods with type P -> T -> U, but
// not instance properties or static members -- the metatype value itself
// doesn't give us a witness so there's no static method to bind.
hasInstanceMethods = true;
} else {
// Metatypes of nominal types and archetypes have instance methods and
// static members, but not instance properties.
// FIXME: partial application of properties
hasInstanceMethods = true;
hasStaticMembers = true;
}
// If we're at the root of an unevaluated context, we can
// reference instance members on the metatype.
if (memberLocator &&
UnevaluatedRootExprs.count(memberLocator->getAnchor())) {
hasInstanceMembers = true;
}
} else {
// Otherwise, we can access all instance members.
hasInstanceMembers = true;
hasInstanceMethods = true;
}
// If the argument labels for this result are incompatible with // If the argument labels for this result are incompatible with
// the call site, skip it. // the call site, skip it.
if (!hasCompatibleArgumentLabels(decl)) { if (!hasCompatibleArgumentLabels(baseObjTy, decl)) {
labelMismatch = true; labelMismatch = true;
result.addUnviable(candidate, MemberLookupResult::UR_LabelMismatch); result.addUnviable(candidate, MemberLookupResult::UR_LabelMismatch);
return; return;
@@ -3102,7 +3099,7 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
// If our base is an existential type, we can't make use of any // If our base is an existential type, we can't make use of any
// member whose signature involves associated types. // member whose signature involves associated types.
if (isExistential) { if (instanceTy->isExistentialType()) {
if (auto *proto = decl->getDeclContext() if (auto *proto = decl->getDeclContext()
->getAsProtocolOrProtocolExtensionContext()) { ->getAsProtocolOrProtocolExtensionContext()) {
if (!proto->isAvailableInExistential(decl)) { if (!proto->isAvailableInExistential(decl)) {
@@ -3141,10 +3138,9 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
// If the underlying type of a typealias is fully concrete, it is legal // If the underlying type of a typealias is fully concrete, it is legal
// to access the type with a protocol metatype base. // to access the type with a protocol metatype base.
} else if (isExistential && } else if (instanceTy->isExistentialType() &&
isa<TypeAliasDecl>(decl) && isa<TypeAliasDecl>(decl) &&
!cast<TypeAliasDecl>(decl)->getInterfaceType()->getCanonicalType() !cast<TypeAliasDecl>(decl)->getInterfaceType()->hasTypeParameter()) {
->hasTypeParameter()) {
/* We're OK */ /* We're OK */
@@ -3158,8 +3154,9 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
// If we have an rvalue base, make sure that the result isn't 'mutating' // If we have an rvalue base, make sure that the result isn't 'mutating'
// (only valid on lvalues). // (only valid on lvalues).
if (!isMetatype && if (!baseTy->is<AnyMetatypeType>() &&
!baseTy->is<LValueType>() && decl->isInstanceMember()) { !baseTy->is<LValueType>() &&
decl->isInstanceMember()) {
if (auto *FD = dyn_cast<FuncDecl>(decl)) if (auto *FD = dyn_cast<FuncDecl>(decl))
if (FD->isMutating()) { if (FD->isMutating()) {
result.addUnviable(candidate, result.addUnviable(candidate,
@@ -3188,7 +3185,7 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
bool isUnwrappedOptional) -> OverloadChoice { bool isUnwrappedOptional) -> OverloadChoice {
// If we're looking into an existential type, check whether this // If we're looking into an existential type, check whether this
// result was found via dynamic lookup. // result was found via dynamic lookup.
if (isDynamicLookup) { if (instanceTy->isAnyObject()) {
assert(cand->getDeclContext()->isTypeContext() && "Dynamic lookup bug"); assert(cand->getDeclContext()->isTypeContext() && "Dynamic lookup bug");
// We found this declaration via dynamic lookup, record it as such. // We found this declaration via dynamic lookup, record it as such.
@@ -3202,9 +3199,8 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
// If we got the choice by unwrapping an optional type, unwrap the base // If we got the choice by unwrapping an optional type, unwrap the base
// type. // type.
Type ovlBaseTy = baseTy;
if (isUnwrappedOptional) { if (isUnwrappedOptional) {
ovlBaseTy = MetatypeType::get(baseTy->castTo<MetatypeType>() auto ovlBaseTy = MetatypeType::get(baseTy->castTo<MetatypeType>()
->getInstanceType() ->getInstanceType()
->getAnyOptionalObjectType()); ->getAnyOptionalObjectType());
return OverloadChoice::getDeclViaUnwrappedOptional(ovlBaseTy, cand, return OverloadChoice::getDeclViaUnwrappedOptional(ovlBaseTy, cand,
@@ -3216,7 +3212,7 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
baseTy, cand, functionRefKind); baseTy, cand, functionRefKind);
} }
return OverloadChoice(ovlBaseTy, cand, functionRefKind); return OverloadChoice(baseTy, cand, functionRefKind);
}; };
// Add all results from this lookup. // Add all results from this lookup.
@@ -3229,8 +3225,8 @@ retry_after_fail:
// If the instance type is a bridged to an Objective-C type, perform // If the instance type is a bridged to an Objective-C type, perform
// a lookup into that Objective-C type. // a lookup into that Objective-C type.
if (bridgedType && !isMetatype) { if (bridgedType) {
LookupResult &bridgedLookup = lookupMember(bridgedClass, memberName); LookupResult &bridgedLookup = lookupMember(bridgedType, memberName);
ModuleDecl *foundationModule = nullptr; ModuleDecl *foundationModule = nullptr;
for (auto result : bridgedLookup) { for (auto result : bridgedLookup) {
// Ignore results from the Objective-C "Foundation" // Ignore results from the Objective-C "Foundation"
@@ -3258,7 +3254,8 @@ retry_after_fail:
// through optional types. // through optional types.
// //
// FIXME: The short-circuit here is lame. // FIXME: The short-circuit here is lame.
if (result.ViableCandidates.empty() && isMetatype && if (result.ViableCandidates.empty() &&
baseObjTy->is<AnyMetatypeType>() &&
constraintKind == ConstraintKind::UnresolvedValueMember) { constraintKind == ConstraintKind::UnresolvedValueMember) {
if (auto objectType = instanceTy->getAnyOptionalObjectType()) { if (auto objectType = instanceTy->getAnyOptionalObjectType()) {
if (objectType->mayHaveMembers()) { if (objectType->mayHaveMembers()) {

View File

@@ -67,3 +67,24 @@ func nestedOptContext() -> Foo?? {
return .none return .none
} }
// <rdar://problem/35945827>
// This should diagnose instead of crashing in SILGen
protocol Horse {
static var palomino: Horse { get }
}
func rideAHorse(_ horse: Horse?) {}
rideAHorse(.palomino)
// expected-error@-1 {{static member 'palomino' cannot be used on protocol metatype 'Horse.Protocol'}}
// FIXME: This should work if the static member is part of a class though
class Donkey {
static var mule: Donkey & Horse { while true {} }
}
func rideAMule(_ mule: (Horse & Donkey)?) {}
rideAMule(.mule)
// expected-error@-1 {{static member 'mule' cannot be used on protocol metatype '(Donkey & Horse).Protocol'}}