From a485e525c53b31eabb7cbd3084a798c8e50de000 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 10 Aug 2016 22:10:35 -0700 Subject: [PATCH] Sema: Allow protocol typealiases to be accessed from expression context, as long as they don't depend on 'Self' Suppose you have this protocol: protocol P { typealias A = Int typealias B = Self } Clearly, 'P.B' does not make sense, because then the type parameter would "leak out" of the existential container. However, 'P.A' is totally fine, it just means 'Int'. Previously, we would allow 'P.A' in type context, and diagnose on 'P.B'. However, due to an oversight, neither one was allowed in expression context, so for example you could not write 'P.A.self', even though that should just mean 'Int.self'. Fix this by generalizing performMemberLookup(), and fix up some diagnostics to be more specific when something is wrong -- we want to avoid talking about typealiases as 'static members', since that doesn't really make much sense. Fixes . --- include/swift/AST/DiagnosticsSema.def | 9 +++--- lib/Sema/CSDiag.cpp | 32 ++++++++++++++++------ lib/Sema/CSSimplify.cpp | 11 +++++++- lib/Sema/ConstraintSystem.cpp | 10 +++++++ lib/Sema/TypeCheckType.cpp | 14 +++++----- test/decl/typealias/associated_types.swift | 8 ++++-- test/decl/typealias/protocol.swift | 18 ++++++++---- 7 files changed, 74 insertions(+), 28 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 4f6553bfca009..8a7fcbd7da934 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -1434,10 +1434,11 @@ NOTE(optional_req_near_match_accessibility,none, // Protocols and existentials ERROR(assoc_type_outside_of_protocol,none, - "cannot use associated type %0 outside of its protocol", (Identifier)) -ERROR(typealias_to_assoc_type_outside_of_protocol,none, - "cannot use typealias %0 of associated type %1 outside of its protocol", - (Identifier, TypeLoc)) + "associated type %0 can only be used with a concrete type or " + "generic parameter base", (Identifier)) +ERROR(typealias_outside_of_protocol,none, + "typealias %0 can only be used with a concrete type or " + "generic parameter base", (Identifier)) ERROR(circular_protocol_def,none, "circular protocol inheritance %0", (StringRef)) diff --git a/lib/Sema/CSDiag.cpp b/lib/Sema/CSDiag.cpp index dfca15e69ad12..6863e71f9b173 100644 --- a/lib/Sema/CSDiag.cpp +++ b/lib/Sema/CSDiag.cpp @@ -2349,14 +2349,32 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy, SourceLoc loc) { SourceRange baseRange = baseExpr ? baseExpr->getSourceRange() : SourceRange(); + Optional Diag; + // If the base of the lookup is a protocol metatype, suggest // to replace the metatype with 'Self' // error saying the lookup cannot be on a protocol metatype if (auto metatypeTy = baseObjTy->getAs()) { - auto Diag = diagnose(loc, - diag::could_not_use_type_member_on_protocol_metatype, - baseObjTy, memberName); - Diag.highlight(baseRange).highlight(nameLoc.getSourceRange()); + assert(metatypeTy->getInstanceType()->isExistentialType()); + + // Give a customized message if we're accessing a member type + // of a protocol -- otherwise a diagnostic talking about + // static members doesn't make a whole lot of sense + if (isa(member)) { + Diag.emplace(diagnose(loc, + diag::typealias_outside_of_protocol, + memberName.getBaseName())); + } else if (isa(member)) { + Diag.emplace(diagnose(loc, + diag::assoc_type_outside_of_protocol, + memberName.getBaseName())); + } else { + Diag.emplace(diagnose(loc, + diag::could_not_use_type_member_on_protocol_metatype, + baseObjTy, memberName)); + } + + Diag->highlight(baseRange).highlight(nameLoc.getSourceRange()); // See through function decl context if (auto parent = CS->DC->getInnermostTypeContext()) { @@ -2365,7 +2383,7 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy, if (auto extensionContext = parent->getAsProtocolExtensionContext()) { if (extensionContext->getDeclaredType()->getCanonicalType() == metatypeTy->getInstanceType()->getCanonicalType()) { - Diag.fixItReplace(baseRange, "Self"); + Diag->fixItReplace(baseRange, "Self"); } } } @@ -2373,10 +2391,6 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy, return; } - // Otherwise the static member lookup was invalid because it was - // called on an instance - Optional Diag; - if (isa(member)) Diag.emplace(diagnose(loc, diag::could_not_use_enum_element_on_instance, memberName)); diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index ea0a169ddf877..945fb0eb46bf4 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -2962,7 +2962,16 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName, result.addUnviable(cand, MemberLookupResult::UR_InstanceMemberOnType); return; } - + + // If the underlying type of a typealias is fully concrete, it is legal + // to access the type with a protocol metatype base. + } else if (isExistential && + isa(cand) && + !cast(cand)->getInterfaceType()->getCanonicalType() + ->hasTypeParameter()) { + + /* We're OK */ + } else { if (!hasStaticMembers) { result.addUnviable(cand, MemberLookupResult::UR_TypeMemberOnInstance); diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index f0f38f9ec2b0a..b91db5c06e00d 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -1197,6 +1197,16 @@ ConstraintSystem::getTypeOfMemberReference( functionRefKind, locator, base); } + // Don't open existentials when accessing typealias members of + // protocols. + if (auto *alias = dyn_cast(value)) { + if (baseObjTy->isExistentialType()) { + auto memberTy = alias->getUnderlyingType(); + auto openedType = FunctionType::get(baseObjTy, memberTy); + return { openedType, memberTy }; + } + } + // Handle associated type lookup as a special case, horribly. // FIXME: This is an awful hack. if (auto assocType = dyn_cast(value)) { diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index a15512b4652c6..ead480fed88b6 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -1228,14 +1228,14 @@ static Type resolveNestedIdentTypeComponent( return ErrorType::get(TC.Context); } - if (auto alias = dyn_cast(member)) { - if (parentTy->isExistentialType() && memberType->hasTypeParameter()) { - if (diagnoseErrors) - TC.diagnose(comp->getIdLoc(), diag::typealias_to_assoc_type_outside_of_protocol, - comp->getIdentifier(), alias->getUnderlyingTypeLoc()); - return ErrorType::get(TC.Context); - } + if (parentTy->isExistentialType() && isa(member) && + memberType->hasTypeParameter()) { + if (diagnoseErrors) + TC.diagnose(comp->getIdLoc(), diag::typealias_outside_of_protocol, + comp->getIdentifier()); + + return ErrorType::get(TC.Context); } // If there are generic arguments, apply them now. diff --git a/test/decl/typealias/associated_types.swift b/test/decl/typealias/associated_types.swift index f835eecaedfea..40f96c1dd7c59 100644 --- a/test/decl/typealias/associated_types.swift +++ b/test/decl/typealias/associated_types.swift @@ -3,13 +3,17 @@ protocol BaseProto { associatedtype AssocTy } -var a: BaseProto.AssocTy = 4 // expected-error{{cannot use associated type 'AssocTy' outside of its protocol}} +var a: BaseProto.AssocTy = 4 +// expected-error@-1{{associated type 'AssocTy' can only be used with a concrete type or generic parameter base}} +var a = BaseProto.AssocTy.self +// expected-error@-1{{associated type 'AssocTy' can only be used with a concrete type or generic parameter base}} protocol DerivedProto : BaseProto { func associated() -> AssocTy // no-warning - func existential() -> BaseProto.AssocTy // expected-error{{cannot use associated type 'AssocTy' outside of its protocol}} + func existential() -> BaseProto.AssocTy + // expected-error@-1{{associated type 'AssocTy' can only be used with a concrete type or generic parameter base}} } diff --git a/test/decl/typealias/protocol.swift b/test/decl/typealias/protocol.swift index 76e99a79f0451..047e18312bce6 100644 --- a/test/decl/typealias/protocol.swift +++ b/test/decl/typealias/protocol.swift @@ -135,13 +135,18 @@ protocol P5 { var a: T2 { get } } +protocol P6 { + typealias A = Int + typealias B = Self +} + struct T5 : P5 { // This is OK -- the typealias is fully concrete var a: P5.T1 // OK // Invalid -- cannot represent associated type of existential - var v2: P5.T2 // expected-error {{cannot use typealias 'T2' of associated type 'A' outside of its protocol}} - var v3: P5.X // expected-error {{cannot use typealias 'X' of associated type 'Self' outside of its protocol}} + var v2: P5.T2 // expected-error {{typealias 'T2' can only be used with a concrete type or generic parameter base}} + var v3: P5.X // expected-error {{typealias 'X' can only be used with a concrete type or generic parameter base}} // FIXME: Unqualified reference to typealias from a protocol conformance var v4: T1 // expected-error {{use of undeclared type 'T1'}} @@ -150,18 +155,21 @@ struct T5 : P5 { // Qualified reference var v6: T5.T1 // OK var v7: T5.T2 // OK + + var v8 = P6.A.self + var v9 = P6.B.self // expected-error {{typealias 'B' can only be used with a concrete type or generic parameter base}} } // Unqualified lookup finds typealiases in protocol extensions, though -protocol P6 { +protocol P7 { associatedtype A } -extension P6 { +extension P7 { typealias Y = A } -struct S6 : P6 { +struct S7 : P7 { typealias A = Int // FIXME