diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 69ada46f6de7b..6b597e146e4e6 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -1117,6 +1117,9 @@ class TypeEraserAttr final : public DeclAttribute { TypeEraserLoc(typeEraserLoc) {} const TypeLoc &getTypeEraserLoc() const { return TypeEraserLoc; } + TypeLoc &getTypeEraserLoc() { return TypeEraserLoc; } + + bool hasViableTypeEraserInit(ProtocolDecl *protocol) const; static bool classof(const DeclAttribute *DA) { return DA->getKind() == DAK_TypeEraser; diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index cb18f72f48422..0391f314dd9e4 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4412,6 +4412,39 @@ ERROR(dynamic_replacement_replaced_constructor_is_convenience, none, ERROR(dynamic_replacement_replaced_constructor_is_not_convenience, none, "replaced constructor %0 is not marked as convenience", (DeclNameRef)) +//------------------------------------------------------------------------------ +// MARK: @_typeEraser() +//------------------------------------------------------------------------------ + +ERROR(non_nominal_type_eraser,none, + "type eraser must be a class, struct, or enum", ()) +ERROR(type_eraser_does_not_conform,none, + "type eraser %0 must conform to protocol %1", (Type, Type)) +ERROR(type_eraser_not_accessible,none, + "%select{private|fileprivate|internal|public|open}0 type eraser %1 " + "cannot have more restrictive access than protocol %2 " + "(which is %select{private|fileprivate|internal|public|open}3)", + (AccessLevel, Identifier, Type, AccessLevel)) +ERROR(type_eraser_missing_init,none, + "type eraser %0 must have an initializer of the form " + "'init(erasing: T)'", (Type, StringRef)) +ERROR(type_eraser_unviable_init,none, + "type eraser %0 has no viable initializer of the form " + "'init(erasing: T)'", (Type, StringRef)) + +NOTE(type_eraser_declared_here,none, + "type eraser declared here",()) +NOTE(type_eraser_failable_init,none, + "'init(erasing:)' cannot be failable",()) +NOTE(type_eraser_init_unsatisfied_requirements,none, + "'init(erasing:)' cannot have unsatisfied requirements " + "when %0 = 'some %1'", (Type, StringRef)) +NOTE(type_eraser_init_not_accessible,none, + "%select{private|fileprivate|internal|public|open}0 'init(erasing:)' " + "cannot have more restrictive access than protocol %1 " + "(which is %select{private|fileprivate|internal|public|open}2)", + (AccessLevel, Type, AccessLevel)) + //------------------------------------------------------------------------------ // MARK: @available //------------------------------------------------------------------------------ diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 0cbb400aa60f2..307fb1b7289b1 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -64,6 +64,7 @@ IDENTIFIER(encode) IDENTIFIER(encodeIfPresent) IDENTIFIER(Encoder) IDENTIFIER(encoder) +IDENTIFIER(erasing) IDENTIFIER(error) IDENTIFIER(errorDomain) IDENTIFIER(first) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 125138553fe8d..7e3257e79d792 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -2054,6 +2054,25 @@ class DifferentiableAttributeTypeCheckRequest void cacheResult(IndexSubset *value) const; }; +/// Checks whether a type eraser has a viable initializer. +class TypeEraserHasViableInitRequest + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation + llvm::Expected evaluate(Evaluator &evaluator, TypeEraserAttr *attr, + ProtocolDecl *protocol) const; + +public: + bool isCached() const { return true; } +}; + // Allow AnyValue to compare two Type values, even though Type doesn't // support ==. template<> diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index e897a4397aef2..9e134f0a917ec 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -46,6 +46,9 @@ SWIFT_REQUEST(TypeChecker, DefaultTypeRequest, SWIFT_REQUEST(TypeChecker, DifferentiableAttributeTypeCheckRequest, IndexSubset *(DifferentiableAttr *), SeparatelyCached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, TypeEraserHasViableInitRequest, + bool(TypeEraserAttr *, ProtocolDecl *), + Cached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, DynamicallyReplacedDeclRequest, ValueDecl *(ValueDecl *), Cached, NoLocationInfo) diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index d031d6a93322c..978edfadf24a3 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1333,6 +1333,14 @@ SourceLoc DynamicReplacementAttr::getRParenLoc() const { return getTrailingLocations()[1]; } +bool +TypeEraserAttr::hasViableTypeEraserInit(ProtocolDecl *protocol) const { + return evaluateOrDefault(protocol->getASTContext().evaluator, + TypeEraserHasViableInitRequest{ + const_cast(this), protocol}, + false); +} + AvailableAttr * AvailableAttr::createPlatformAgnostic(ASTContext &C, StringRef Message, diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 3f4e9a4048e3a..8c58f76bae6ce 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -108,7 +108,6 @@ class AttributeChecker : public AttributeVisitor { IGNORED_ATTR(StaticInitializeObjCMetadata) IGNORED_ATTR(SynthesizedProtocol) IGNORED_ATTR(Testable) - IGNORED_ATTR(TypeEraser) IGNORED_ATTR(WeakLinked) IGNORED_ATTR(PrivateImport) IGNORED_ATTR(DisfavoredOverload) @@ -239,6 +238,7 @@ class AttributeChecker : public AttributeVisitor { void visitDiscardableResultAttr(DiscardableResultAttr *attr); void visitDynamicReplacementAttr(DynamicReplacementAttr *attr); + void visitTypeEraserAttr(TypeEraserAttr *attr); void visitImplementsAttr(ImplementsAttr *attr); void visitFrozenAttr(FrozenAttr *attr); @@ -2376,6 +2376,179 @@ void AttributeChecker::visitDynamicReplacementAttr(DynamicReplacementAttr *attr) } } +llvm::Expected +TypeEraserHasViableInitRequest::evaluate(Evaluator &evaluator, + TypeEraserAttr *attr, + ProtocolDecl *protocol) const { + auto &ctx = protocol->getASTContext(); + auto &diags = ctx.Diags; + TypeLoc &typeEraserLoc = attr->getTypeEraserLoc(); + TypeRepr *typeEraserRepr = typeEraserLoc.getTypeRepr(); + DeclContext *dc = protocol->getDeclContext(); + Type protocolType = protocol->getDeclaredType(); + + // Get the NominalTypeDecl for the type eraser. + Type typeEraser = typeEraserLoc.getType(); + if (!typeEraser && typeEraserRepr) { + auto resolution = TypeResolution::forContextual(protocol); + typeEraser = resolution.resolveType(typeEraserRepr, /*options=*/None); + typeEraserLoc.setType(typeEraser); + } + + if (typeEraser->hasError()) + return false; + + // The type eraser must be a concrete nominal type + auto nominalTypeDecl = typeEraser->getAnyNominal(); + if (auto typeAliasDecl = dyn_cast_or_null(nominalTypeDecl)) + nominalTypeDecl = typeAliasDecl->getUnderlyingType()->getAnyNominal(); + + if (!nominalTypeDecl || isa(nominalTypeDecl)) { + diags.diagnose(typeEraserLoc.getLoc(), diag::non_nominal_type_eraser); + return false; + } + + // The nominal type must be accessible wherever the protocol is accessible + if (nominalTypeDecl->getFormalAccess() < protocol->getFormalAccess()) { + diags.diagnose(typeEraserLoc.getLoc(), diag::type_eraser_not_accessible, + nominalTypeDecl->getFormalAccess(), nominalTypeDecl->getName(), + protocolType, protocol->getFormalAccess()); + diags.diagnose(nominalTypeDecl->getLoc(), diag::type_eraser_declared_here); + return false; + } + + // The type eraser must conform to the annotated protocol + if (!TypeChecker::conformsToProtocol(typeEraser, protocol, dc, None)) { + diags.diagnose(typeEraserLoc.getLoc(), diag::type_eraser_does_not_conform, + typeEraser, protocolType); + diags.diagnose(nominalTypeDecl->getLoc(), diag::type_eraser_declared_here); + return false; + } + + // The type eraser must have an init of the form init(erasing: T) + auto lookupResult = TypeChecker::lookupMember(dc, typeEraser, + DeclNameRef::createConstructor()); + + // Keep track of unviable init candidates for diagnostics + enum class UnviableReason { + Failable, + UnsatisfiedRequirements, + Inaccessible, + }; + SmallVector, 2> unviable; + + bool foundMatch = llvm::any_of(lookupResult, [&](const LookupResultEntry &entry) { + auto *init = cast(entry.getValueDecl()); + if (!init->isGeneric() || init->getGenericParams()->size() != 1) + return false; + + auto genericSignature = init->getGenericSignature(); + auto genericParamType = genericSignature->getInnermostGenericParams().front(); + + // Fow now, only allow one parameter. + auto params = init->getParameters(); + if (params->size() != 1) + return false; + + // The parameter must have the form `erasing: T` where T conforms to the protocol. + ParamDecl *param = *init->getParameters()->begin(); + if (param->getArgumentName() != ctx.Id_erasing || + !param->getInterfaceType()->isEqual(genericParamType) || + !genericSignature->conformsToProtocol(genericParamType, protocol)) + return false; + + // Allow other constraints as long as the init can be called with any + // type conforming to the annotated protocol. We will check this by + // substituting the protocol's Self type for the generic arg and check that + // the requirements in the generic signature are satisfied. + auto baseMap = + typeEraser->getContextSubstitutionMap(nominalTypeDecl->getParentModule(), + nominalTypeDecl); + QuerySubstitutionMap getSubstitution{baseMap}; + auto subMap = SubstitutionMap::get( + genericSignature, + [&](SubstitutableType *type) -> Type { + if (type->isEqual(genericParamType)) + return protocol->getSelfTypeInContext(); + + return getSubstitution(type); + }, + TypeChecker::LookUpConformance(dc)); + + // Use invalid 'SourceLoc's to suppress diagnostics. + auto result = TypeChecker::checkGenericArguments( + protocol, SourceLoc(), SourceLoc(), typeEraser, + genericSignature->getGenericParams(), + genericSignature->getRequirements(), + QuerySubstitutionMap{subMap}, + TypeChecker::LookUpConformance(dc), + None); + + if (result != RequirementCheckResult::Success) { + unviable.push_back( + std::make_tuple(init, UnviableReason::UnsatisfiedRequirements, + genericParamType)); + return false; + } + + if (init->isFailable()) { + unviable.push_back( + std::make_tuple(init, UnviableReason::Failable, genericParamType)); + return false; + } + + if (init->getFormalAccess() < protocol->getFormalAccess()) { + unviable.push_back( + std::make_tuple(init, UnviableReason::Inaccessible, genericParamType)); + return false; + } + + return true; + }); + + if (!foundMatch) { + if (unviable.empty()) { + diags.diagnose(attr->getLocation(), diag::type_eraser_missing_init, + typeEraser, protocol->getName().str()); + diags.diagnose(nominalTypeDecl->getLoc(), diag::type_eraser_declared_here); + return false; + } + + diags.diagnose(attr->getLocation(), diag::type_eraser_unviable_init, + typeEraser, protocol->getName().str()); + for (auto &candidate: unviable) { + auto init = std::get<0>(candidate); + auto reason = std::get<1>(candidate); + auto genericParamType = std::get<2>(candidate); + + switch (reason) { + case UnviableReason::Failable: + diags.diagnose(init->getLoc(), diag::type_eraser_failable_init); + break; + case UnviableReason::UnsatisfiedRequirements: + diags.diagnose(init->getLoc(), + diag::type_eraser_init_unsatisfied_requirements, + genericParamType, protocol->getName().str()); + break; + case UnviableReason::Inaccessible: + diags.diagnose(init->getLoc(), diag::type_eraser_init_not_accessible, + init->getFormalAccess(), protocolType, + protocol->getFormalAccess()); + break; + } + } + return false; + } + + return true; +} + +void AttributeChecker::visitTypeEraserAttr(TypeEraserAttr *attr) { + assert(isa(D)); + // Invoke the request. + (void)attr->hasViableTypeEraserInit(cast(D)); +} + void AttributeChecker::visitImplementsAttr(ImplementsAttr *attr) { TypeLoc &ProtoTypeLoc = attr->getProtocolType(); diff --git a/test/attr/typeEraser.swift b/test/attr/typeEraser.swift index 7f05ab76d69e8..b8607c8f5af0a 100644 --- a/test/attr/typeEraser.swift +++ b/test/attr/typeEraser.swift @@ -1,19 +1,126 @@ // RUN: %target-swift-frontend -typecheck %s -verify -class AnyP: P1 {} +// MARK: - Correct type eraser +class AnyP: P1 { + init(erasing t: T) {} +} @_typeEraser(AnyP) // okay protocol P1 {} -@_typeEraser // expected-error {{expected '(' in '_typeEraser' attribute}} +class AnyP2: P2 { + init(erasing t: T) {} +} +typealias AnyP2Alias = AnyP2 +@_typeEraser(AnyP2Alias) protocol P2 {} +class AnyCollection : Collection { + typealias Element = Element + init(erasing c: C) where Element == C.Element {} +} +@_typeEraser(AnyCollection) +protocol Collection { + associatedtype Element +} + +// MARK: - Parsing Errors + +@_typeEraser // expected-error {{expected '(' in '_typeEraser' attribute}} +protocol A1 {} + @_typeEraser() // expected-error {{expected a type name in @_typeEraser()}} -protocol P3 {} +protocol A2 {} @_typeEraser(AnyP // expected-note {{to match this opening '('}} -protocol P4 {} // expected-error {{expected ')' after type name for @_typeEraser}} +protocol A3 {} // expected-error {{expected ')' after type name for @_typeEraser}} @_typeEraser(AnyP) // expected-error {{@_typeEraser may only be used on 'protocol' declarations}} func notAProtocol() {} +// MARK: - Type eraser must be a concrete nominal type + +@_typeEraser(Undeclared) // expected-error {{use of undeclared type 'Undeclared'}} +protocol B1 {} + +@_typeEraser((Int, Int)) // expected-error {{type eraser must be a class, struct, or enum}} +protocol B2 {} + +protocol InvalidTypeEraser {} +@_typeEraser(InvalidTypeEraser) // expected-error {{type eraser must be a class, struct, or enum}} +protocol B3 {} + +class Generic: B5 { // expected-note {{generic type 'Generic' declared here}} + init(erasing t: T) {} +} +@_typeEraser(Generic) // expected-error {{reference to generic type 'Generic' requires arguments in <...>}} +protocol B4 {} +@_typeEraser(Generic) // bound generic is okay +protocol B5 {} + +class MoreRestrictive: B6 { // expected-note {{type eraser declared here}} + init(erasing t: T) {} +} +@_typeEraser(MoreRestrictive) // expected-error {{internal type eraser 'MoreRestrictive' cannot have more restrictive access than protocol 'B6' (which is public)}} +public protocol B6 {} + +typealias FnAlias = () -> Void +@_typeEraser(FnAlias) // expected-error {{type eraser must be a class, struct, or enum}} +protocol B7 {} + +// MARK: - Type eraser must conform to the annotated protocol + +class DoesNotConform {} // expected-note {{type eraser declared here}} +@_typeEraser(DoesNotConform) // expected-error {{type eraser 'DoesNotConform' must conform to protocol 'C1'}} +protocol C1 {} + +// MARK: - Type eraser must have an initializer in the form init(erasing: T) with T constrained to annotated protocol + +class NoArgInit: D1 {} // expected-note {{type eraser declared here}} +@_typeEraser(NoArgInit) // expected-error {{type eraser 'NoArgInit' must have an initializer of the form 'init(erasing: T)'}} +protocol D1 {} + +class InvalidArgInit: D2 { // expected-note {{type eraser declared here}} + init(erasing t: T) {} +} +@_typeEraser(InvalidArgInit) // expected-error {{type eraser 'InvalidArgInit' must have an initializer of the form 'init(erasing: T)'}} +protocol D2 {} + +class ExtraArgInit: D3 { // expected-note {{type eraser declared here}} + init(erasing t: T, extraArg: Int) {} +} +@_typeEraser(ExtraArgInit) // expected-error {{type eraser 'ExtraArgInit' must have an initializer of the form 'init(erasing: T)'}} +protocol D3 {} + +class WrongLabelInit: D4 { // expected-note {{type eraser declared here}} + init(wrongLabel: T) {} +} +@_typeEraser(WrongLabelInit) // expected-error {{type eraser 'WrongLabelInit' must have an initializer of the form 'init(erasing: T)'}} +protocol D4 {} + +class NoLabel: D5 { // expected-note {{type eraser declared here}} + init(_ t: T) {} +} +@_typeEraser(NoLabel) // expected-error {{type eraser 'NoLabel' must have an initializer of the form 'init(erasing: T)'}} +protocol D5 {} + +class NonGenericInit: D6 { // expected-note {{type eraser declared here}} + init(_ t: T) {} +} +@_typeEraser(NonGenericInit) // expected-error {{type eraser 'NonGenericInit' must have an initializer of the form 'init(erasing: T)'}} +protocol D6 {} + +// MARK: - Unviable initializers + +public class UnviableInits: E1 { + public init(erasing t: T) where T: Hashable {} // expected-note {{'init(erasing:)' cannot have unsatisfied requirements when 'T' = 'some E1'}} + init(erasing t: T) {} // expected-note {{internal 'init(erasing:)' cannot have more restrictive access than protocol 'E1' (which is public)}} +} +@_typeEraser(UnviableInits) // expected-error {{type eraser 'UnviableInits' has no viable initializer of the form 'init(erasing: T)'}} +public protocol E1 {} + +class FailableInit: E2 { + init?(erasing t: T) {} // expected-note {{'init(erasing:)' cannot be failable}} +} +@_typeEraser(FailableInit) // expected-error {{type eraser 'FailableInit' has no viable initializer of the form 'init(erasing: T)'}} +protocol E2 {}