Skip to content
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
3 changes: 3 additions & 0 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
33 changes: 33 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: %1>(erasing: T)'", (Type, StringRef))
ERROR(type_eraser_unviable_init,none,
"type eraser %0 has no viable initializer of the form "
"'init<T: %1>(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
//------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ IDENTIFIER(encode)
IDENTIFIER(encodeIfPresent)
IDENTIFIER(Encoder)
IDENTIFIER(encoder)
IDENTIFIER(erasing)
IDENTIFIER(error)
IDENTIFIER(errorDomain)
IDENTIFIER(first)
Expand Down
19 changes: 19 additions & 0 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -2054,6 +2054,25 @@ class DifferentiableAttributeTypeCheckRequest
void cacheResult(IndexSubset *value) const;
};

/// Checks whether a type eraser has a viable initializer.
class TypeEraserHasViableInitRequest
: public SimpleRequest<TypeEraserHasViableInitRequest,
bool(TypeEraserAttr *, ProtocolDecl *),
CacheKind::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

// Evaluation
llvm::Expected<bool> 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<>
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TypeEraserAttr *>(this), protocol},
false);
}

AvailableAttr *
AvailableAttr::createPlatformAgnostic(ASTContext &C,
StringRef Message,
Expand Down
175 changes: 174 additions & 1 deletion lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
IGNORED_ATTR(StaticInitializeObjCMetadata)
IGNORED_ATTR(SynthesizedProtocol)
IGNORED_ATTR(Testable)
IGNORED_ATTR(TypeEraser)
IGNORED_ATTR(WeakLinked)
IGNORED_ATTR(PrivateImport)
IGNORED_ATTR(DisfavoredOverload)
Expand Down Expand Up @@ -239,6 +238,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {

void visitDiscardableResultAttr(DiscardableResultAttr *attr);
void visitDynamicReplacementAttr(DynamicReplacementAttr *attr);
void visitTypeEraserAttr(TypeEraserAttr *attr);
void visitImplementsAttr(ImplementsAttr *attr);

void visitFrozenAttr(FrozenAttr *attr);
Expand Down Expand Up @@ -2376,6 +2376,179 @@ void AttributeChecker::visitDynamicReplacementAttr(DynamicReplacementAttr *attr)
}
}

llvm::Expected<bool>
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<TypeAliasDecl>(nominalTypeDecl))
nominalTypeDecl = typeAliasDecl->getUnderlyingType()->getAnyNominal();

if (!nominalTypeDecl || isa<ProtocolDecl>(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<T: Protocol>(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<std::tuple<ConstructorDecl *, UnviableReason, Type>, 2> unviable;

bool foundMatch = llvm::any_of(lookupResult, [&](const LookupResultEntry &entry) {
auto *init = cast<ConstructorDecl>(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<ProtocolDecl>(D));
// Invoke the request.
(void)attr->hasViableTypeEraserInit(cast<ProtocolDecl>(D));
}

void AttributeChecker::visitImplementsAttr(ImplementsAttr *attr) {
TypeLoc &ProtoTypeLoc = attr->getProtocolType();

Expand Down
Loading