Skip to content

Commit 9f09add

Browse files
authored
Merge pull request swiftlang#29966 from hborla/type-check-type-eraser-attribute
[Sema] Implement type checking for the `_typeEraser` attribute
2 parents 7802d17 + e93a6b9 commit 9f09add

File tree

8 files changed

+352
-5
lines changed

8 files changed

+352
-5
lines changed

include/swift/AST/Attr.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,9 @@ class TypeEraserAttr final : public DeclAttribute {
11171117
TypeEraserLoc(typeEraserLoc) {}
11181118

11191119
const TypeLoc &getTypeEraserLoc() const { return TypeEraserLoc; }
1120+
TypeLoc &getTypeEraserLoc() { return TypeEraserLoc; }
1121+
1122+
bool hasViableTypeEraserInit(ProtocolDecl *protocol) const;
11201123

11211124
static bool classof(const DeclAttribute *DA) {
11221125
return DA->getKind() == DAK_TypeEraser;

include/swift/AST/DiagnosticsSema.def

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4389,6 +4389,39 @@ ERROR(dynamic_replacement_replaced_constructor_is_convenience, none,
43894389
ERROR(dynamic_replacement_replaced_constructor_is_not_convenience, none,
43904390
"replaced constructor %0 is not marked as convenience", (DeclNameRef))
43914391

4392+
//------------------------------------------------------------------------------
4393+
// MARK: @_typeEraser()
4394+
//------------------------------------------------------------------------------
4395+
4396+
ERROR(non_nominal_type_eraser,none,
4397+
"type eraser must be a class, struct, or enum", ())
4398+
ERROR(type_eraser_does_not_conform,none,
4399+
"type eraser %0 must conform to protocol %1", (Type, Type))
4400+
ERROR(type_eraser_not_accessible,none,
4401+
"%select{private|fileprivate|internal|public|open}0 type eraser %1 "
4402+
"cannot have more restrictive access than protocol %2 "
4403+
"(which is %select{private|fileprivate|internal|public|open}3)",
4404+
(AccessLevel, Identifier, Type, AccessLevel))
4405+
ERROR(type_eraser_missing_init,none,
4406+
"type eraser %0 must have an initializer of the form "
4407+
"'init<T: %1>(erasing: T)'", (Type, StringRef))
4408+
ERROR(type_eraser_unviable_init,none,
4409+
"type eraser %0 has no viable initializer of the form "
4410+
"'init<T: %1>(erasing: T)'", (Type, StringRef))
4411+
4412+
NOTE(type_eraser_declared_here,none,
4413+
"type eraser declared here",())
4414+
NOTE(type_eraser_failable_init,none,
4415+
"'init(erasing:)' cannot be failable",())
4416+
NOTE(type_eraser_init_unsatisfied_requirements,none,
4417+
"'init(erasing:)' cannot have unsatisfied requirements "
4418+
"when %0 = 'some %1'", (Type, StringRef))
4419+
NOTE(type_eraser_init_not_accessible,none,
4420+
"%select{private|fileprivate|internal|public|open}0 'init(erasing:)' "
4421+
"cannot have more restrictive access than protocol %1 "
4422+
"(which is %select{private|fileprivate|internal|public|open}2)",
4423+
(AccessLevel, Type, AccessLevel))
4424+
43924425
//------------------------------------------------------------------------------
43934426
// MARK: @available
43944427
//------------------------------------------------------------------------------

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ IDENTIFIER(encode)
6464
IDENTIFIER(encodeIfPresent)
6565
IDENTIFIER(Encoder)
6666
IDENTIFIER(encoder)
67+
IDENTIFIER(erasing)
6768
IDENTIFIER(error)
6869
IDENTIFIER(errorDomain)
6970
IDENTIFIER(first)

include/swift/AST/TypeCheckRequests.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2054,6 +2054,25 @@ class DifferentiableAttributeTypeCheckRequest
20542054
void cacheResult(IndexSubset *value) const;
20552055
};
20562056

2057+
/// Checks whether a type eraser has a viable initializer.
2058+
class TypeEraserHasViableInitRequest
2059+
: public SimpleRequest<TypeEraserHasViableInitRequest,
2060+
bool(TypeEraserAttr *, ProtocolDecl *),
2061+
CacheKind::Cached> {
2062+
public:
2063+
using SimpleRequest::SimpleRequest;
2064+
2065+
private:
2066+
friend SimpleRequest;
2067+
2068+
// Evaluation
2069+
llvm::Expected<bool> evaluate(Evaluator &evaluator, TypeEraserAttr *attr,
2070+
ProtocolDecl *protocol) const;
2071+
2072+
public:
2073+
bool isCached() const { return true; }
2074+
};
2075+
20572076
// Allow AnyValue to compare two Type values, even though Type doesn't
20582077
// support ==.
20592078
template<>

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ SWIFT_REQUEST(TypeChecker, DefaultTypeRequest,
4646
SWIFT_REQUEST(TypeChecker, DifferentiableAttributeTypeCheckRequest,
4747
IndexSubset *(DifferentiableAttr *),
4848
SeparatelyCached, NoLocationInfo)
49+
SWIFT_REQUEST(TypeChecker, TypeEraserHasViableInitRequest,
50+
bool(TypeEraserAttr *, ProtocolDecl *),
51+
Cached, NoLocationInfo)
4952
SWIFT_REQUEST(TypeChecker, DynamicallyReplacedDeclRequest,
5053
ValueDecl *(ValueDecl *),
5154
Cached, NoLocationInfo)

lib/AST/Attr.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,14 @@ SourceLoc DynamicReplacementAttr::getRParenLoc() const {
13501350
return getTrailingLocations()[1];
13511351
}
13521352

1353+
bool
1354+
TypeEraserAttr::hasViableTypeEraserInit(ProtocolDecl *protocol) const {
1355+
return evaluateOrDefault(protocol->getASTContext().evaluator,
1356+
TypeEraserHasViableInitRequest{
1357+
const_cast<TypeEraserAttr *>(this), protocol},
1358+
false);
1359+
}
1360+
13531361
AvailableAttr *
13541362
AvailableAttr::createPlatformAgnostic(ASTContext &C,
13551363
StringRef Message,

lib/Sema/TypeCheckAttr.cpp

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
108108
IGNORED_ATTR(StaticInitializeObjCMetadata)
109109
IGNORED_ATTR(SynthesizedProtocol)
110110
IGNORED_ATTR(Testable)
111-
IGNORED_ATTR(TypeEraser)
112111
IGNORED_ATTR(WeakLinked)
113112
IGNORED_ATTR(PrivateImport)
114113
IGNORED_ATTR(DisfavoredOverload)
@@ -240,6 +239,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
240239

241240
void visitDiscardableResultAttr(DiscardableResultAttr *attr);
242241
void visitDynamicReplacementAttr(DynamicReplacementAttr *attr);
242+
void visitTypeEraserAttr(TypeEraserAttr *attr);
243243
void visitImplementsAttr(ImplementsAttr *attr);
244244

245245
void visitFrozenAttr(FrozenAttr *attr);
@@ -2390,6 +2390,179 @@ void AttributeChecker::visitDynamicReplacementAttr(DynamicReplacementAttr *attr)
23902390
}
23912391
}
23922392

2393+
llvm::Expected<bool>
2394+
TypeEraserHasViableInitRequest::evaluate(Evaluator &evaluator,
2395+
TypeEraserAttr *attr,
2396+
ProtocolDecl *protocol) const {
2397+
auto &ctx = protocol->getASTContext();
2398+
auto &diags = ctx.Diags;
2399+
TypeLoc &typeEraserLoc = attr->getTypeEraserLoc();
2400+
TypeRepr *typeEraserRepr = typeEraserLoc.getTypeRepr();
2401+
DeclContext *dc = protocol->getDeclContext();
2402+
Type protocolType = protocol->getDeclaredType();
2403+
2404+
// Get the NominalTypeDecl for the type eraser.
2405+
Type typeEraser = typeEraserLoc.getType();
2406+
if (!typeEraser && typeEraserRepr) {
2407+
auto resolution = TypeResolution::forContextual(protocol);
2408+
typeEraser = resolution.resolveType(typeEraserRepr, /*options=*/None);
2409+
typeEraserLoc.setType(typeEraser);
2410+
}
2411+
2412+
if (typeEraser->hasError())
2413+
return false;
2414+
2415+
// The type eraser must be a concrete nominal type
2416+
auto nominalTypeDecl = typeEraser->getAnyNominal();
2417+
if (auto typeAliasDecl = dyn_cast_or_null<TypeAliasDecl>(nominalTypeDecl))
2418+
nominalTypeDecl = typeAliasDecl->getUnderlyingType()->getAnyNominal();
2419+
2420+
if (!nominalTypeDecl || isa<ProtocolDecl>(nominalTypeDecl)) {
2421+
diags.diagnose(typeEraserLoc.getLoc(), diag::non_nominal_type_eraser);
2422+
return false;
2423+
}
2424+
2425+
// The nominal type must be accessible wherever the protocol is accessible
2426+
if (nominalTypeDecl->getFormalAccess() < protocol->getFormalAccess()) {
2427+
diags.diagnose(typeEraserLoc.getLoc(), diag::type_eraser_not_accessible,
2428+
nominalTypeDecl->getFormalAccess(), nominalTypeDecl->getName(),
2429+
protocolType, protocol->getFormalAccess());
2430+
diags.diagnose(nominalTypeDecl->getLoc(), diag::type_eraser_declared_here);
2431+
return false;
2432+
}
2433+
2434+
// The type eraser must conform to the annotated protocol
2435+
if (!TypeChecker::conformsToProtocol(typeEraser, protocol, dc, None)) {
2436+
diags.diagnose(typeEraserLoc.getLoc(), diag::type_eraser_does_not_conform,
2437+
typeEraser, protocolType);
2438+
diags.diagnose(nominalTypeDecl->getLoc(), diag::type_eraser_declared_here);
2439+
return false;
2440+
}
2441+
2442+
// The type eraser must have an init of the form init<T: Protocol>(erasing: T)
2443+
auto lookupResult = TypeChecker::lookupMember(dc, typeEraser,
2444+
DeclNameRef::createConstructor());
2445+
2446+
// Keep track of unviable init candidates for diagnostics
2447+
enum class UnviableReason {
2448+
Failable,
2449+
UnsatisfiedRequirements,
2450+
Inaccessible,
2451+
};
2452+
SmallVector<std::tuple<ConstructorDecl *, UnviableReason, Type>, 2> unviable;
2453+
2454+
bool foundMatch = llvm::any_of(lookupResult, [&](const LookupResultEntry &entry) {
2455+
auto *init = cast<ConstructorDecl>(entry.getValueDecl());
2456+
if (!init->isGeneric() || init->getGenericParams()->size() != 1)
2457+
return false;
2458+
2459+
auto genericSignature = init->getGenericSignature();
2460+
auto genericParamType = genericSignature->getInnermostGenericParams().front();
2461+
2462+
// Fow now, only allow one parameter.
2463+
auto params = init->getParameters();
2464+
if (params->size() != 1)
2465+
return false;
2466+
2467+
// The parameter must have the form `erasing: T` where T conforms to the protocol.
2468+
ParamDecl *param = *init->getParameters()->begin();
2469+
if (param->getArgumentName() != ctx.Id_erasing ||
2470+
!param->getInterfaceType()->isEqual(genericParamType) ||
2471+
!genericSignature->conformsToProtocol(genericParamType, protocol))
2472+
return false;
2473+
2474+
// Allow other constraints as long as the init can be called with any
2475+
// type conforming to the annotated protocol. We will check this by
2476+
// substituting the protocol's Self type for the generic arg and check that
2477+
// the requirements in the generic signature are satisfied.
2478+
auto baseMap =
2479+
typeEraser->getContextSubstitutionMap(nominalTypeDecl->getParentModule(),
2480+
nominalTypeDecl);
2481+
QuerySubstitutionMap getSubstitution{baseMap};
2482+
auto subMap = SubstitutionMap::get(
2483+
genericSignature,
2484+
[&](SubstitutableType *type) -> Type {
2485+
if (type->isEqual(genericParamType))
2486+
return protocol->getSelfTypeInContext();
2487+
2488+
return getSubstitution(type);
2489+
},
2490+
TypeChecker::LookUpConformance(dc));
2491+
2492+
// Use invalid 'SourceLoc's to suppress diagnostics.
2493+
auto result = TypeChecker::checkGenericArguments(
2494+
protocol, SourceLoc(), SourceLoc(), typeEraser,
2495+
genericSignature->getGenericParams(),
2496+
genericSignature->getRequirements(),
2497+
QuerySubstitutionMap{subMap},
2498+
TypeChecker::LookUpConformance(dc),
2499+
None);
2500+
2501+
if (result != RequirementCheckResult::Success) {
2502+
unviable.push_back(
2503+
std::make_tuple(init, UnviableReason::UnsatisfiedRequirements,
2504+
genericParamType));
2505+
return false;
2506+
}
2507+
2508+
if (init->isFailable()) {
2509+
unviable.push_back(
2510+
std::make_tuple(init, UnviableReason::Failable, genericParamType));
2511+
return false;
2512+
}
2513+
2514+
if (init->getFormalAccess() < protocol->getFormalAccess()) {
2515+
unviable.push_back(
2516+
std::make_tuple(init, UnviableReason::Inaccessible, genericParamType));
2517+
return false;
2518+
}
2519+
2520+
return true;
2521+
});
2522+
2523+
if (!foundMatch) {
2524+
if (unviable.empty()) {
2525+
diags.diagnose(attr->getLocation(), diag::type_eraser_missing_init,
2526+
typeEraser, protocol->getName().str());
2527+
diags.diagnose(nominalTypeDecl->getLoc(), diag::type_eraser_declared_here);
2528+
return false;
2529+
}
2530+
2531+
diags.diagnose(attr->getLocation(), diag::type_eraser_unviable_init,
2532+
typeEraser, protocol->getName().str());
2533+
for (auto &candidate: unviable) {
2534+
auto init = std::get<0>(candidate);
2535+
auto reason = std::get<1>(candidate);
2536+
auto genericParamType = std::get<2>(candidate);
2537+
2538+
switch (reason) {
2539+
case UnviableReason::Failable:
2540+
diags.diagnose(init->getLoc(), diag::type_eraser_failable_init);
2541+
break;
2542+
case UnviableReason::UnsatisfiedRequirements:
2543+
diags.diagnose(init->getLoc(),
2544+
diag::type_eraser_init_unsatisfied_requirements,
2545+
genericParamType, protocol->getName().str());
2546+
break;
2547+
case UnviableReason::Inaccessible:
2548+
diags.diagnose(init->getLoc(), diag::type_eraser_init_not_accessible,
2549+
init->getFormalAccess(), protocolType,
2550+
protocol->getFormalAccess());
2551+
break;
2552+
}
2553+
}
2554+
return false;
2555+
}
2556+
2557+
return true;
2558+
}
2559+
2560+
void AttributeChecker::visitTypeEraserAttr(TypeEraserAttr *attr) {
2561+
assert(isa<ProtocolDecl>(D));
2562+
// Invoke the request.
2563+
(void)attr->hasViableTypeEraserInit(cast<ProtocolDecl>(D));
2564+
}
2565+
23932566
void AttributeChecker::visitImplementsAttr(ImplementsAttr *attr) {
23942567
TypeLoc &ProtoTypeLoc = attr->getProtocolType();
23952568

0 commit comments

Comments
 (0)