Skip to content

Sema: Allow protocol typealiases to be accessed from expression conte… #4224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
32 changes: 23 additions & 9 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2349,14 +2349,32 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy,
SourceLoc loc) {
SourceRange baseRange = baseExpr ? baseExpr->getSourceRange() : SourceRange();

Optional<InFlightDiagnostic> 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<MetatypeType>()) {
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<TypeAliasDecl>(member)) {
Diag.emplace(diagnose(loc,
diag::typealias_outside_of_protocol,
memberName.getBaseName()));
} else if (isa<AssociatedTypeDecl>(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()) {
Expand All @@ -2365,18 +2383,14 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy,
if (auto extensionContext = parent->getAsProtocolExtensionContext()) {
if (extensionContext->getDeclaredType()->getCanonicalType()
== metatypeTy->getInstanceType()->getCanonicalType()) {
Diag.fixItReplace(baseRange, "Self");
Diag->fixItReplace(baseRange, "Self");
}
}
}

return;
}

// Otherwise the static member lookup was invalid because it was
// called on an instance
Optional<InFlightDiagnostic> Diag;

if (isa<EnumElementDecl>(member))
Diag.emplace(diagnose(loc, diag::could_not_use_enum_element_on_instance,
memberName));
Expand Down
11 changes: 10 additions & 1 deletion lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TypeAliasDecl>(cand) &&
!cast<TypeAliasDecl>(cand)->getInterfaceType()->getCanonicalType()
->hasTypeParameter()) {

/* We're OK */

} else {
if (!hasStaticMembers) {
result.addUnviable(cand, MemberLookupResult::UR_TypeMemberOnInstance);
Expand Down
10 changes: 10 additions & 0 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,16 @@ ConstraintSystem::getTypeOfMemberReference(
functionRefKind, locator, base);
}

// Don't open existentials when accessing typealias members of
// protocols.
if (auto *alias = dyn_cast<TypeAliasDecl>(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<AssociatedTypeDecl>(value)) {
Expand Down
14 changes: 7 additions & 7 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1228,14 +1228,14 @@ static Type resolveNestedIdentTypeComponent(

return ErrorType::get(TC.Context);
}
if (auto alias = dyn_cast<TypeAliasDecl>(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<TypeAliasDecl>(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.
Expand Down
8 changes: 6 additions & 2 deletions test/decl/typealias/associated_types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
}


Expand Down
18 changes: 13 additions & 5 deletions test/decl/typealias/protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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'}}
Expand All @@ -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
Expand Down