Skip to content

Commit 87bb775

Browse files
authored
Merge pull request #30101 from hborla/dynamic-replacement-type-erasure
[Sema] Implement type erasure for dynamic replacement.
2 parents 7fc6c13 + 5ce5096 commit 87bb775

File tree

11 files changed

+213
-2
lines changed

11 files changed

+213
-2
lines changed

include/swift/AST/Attr.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ SIMPLE_DECL_ATTR(_inheritsConvenienceInitializers,
520520
93)
521521

522522
DECL_ATTR(_typeEraser, TypeEraser,
523-
OnProtocol | UserInaccessible | NotSerialized |
523+
OnProtocol | UserInaccessible |
524524
ABIStableToAdd | ABIBreakingToRemove | APIStableToAdd | APIBreakingToRemove,
525525
94)
526526

lib/AST/Attr.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,20 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
985985
break;
986986
}
987987

988+
case DAK_TypeEraser: {
989+
Printer.printAttrName("@_typeEraser");
990+
Printer << "(";
991+
Printer.callPrintNamePre(PrintNameContext::Attribute);
992+
auto typeLoc = cast<TypeEraserAttr>(this)->getTypeEraserLoc();
993+
if (auto type = typeLoc.getType())
994+
type->print(Printer, Options);
995+
else
996+
typeLoc.getTypeRepr()->print(Printer, Options);
997+
Printer.printNamePost(PrintNameContext::Attribute);
998+
Printer << ")";
999+
break;
1000+
}
1001+
9881002
case DAK_Custom: {
9891003
Printer.callPrintNamePre(PrintNameContext::Attribute);
9901004
Printer << "@";

lib/Sema/BuilderTransform.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ class BuilderClosureVisitor
210210
return None;
211211

212212
applied.returnExpr = buildVarRef(bodyVar, stmt->getEndLoc());
213+
applied.returnExpr = cs->buildTypeErasedExpr(applied.returnExpr,
214+
dc, applied.bodyResultType,
215+
CTP_ReturnStmt);
216+
213217
applied.returnExpr = cs->generateConstraints(applied.returnExpr, dc);
214218
if (!applied.returnExpr) {
215219
hadError = true;

lib/Sema/CSApply.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7498,6 +7498,7 @@ ExprWalker::rewriteTarget(SolutionApplicationTarget target) {
74987498
return None;
74997499

75007500
result.setFunctionBody(newBody);
7501+
fn.getAbstractFunctionDecl()->setHasSingleExpressionBody(false);
75017502
}
75027503

75037504
// Follow-up tasks.

lib/Sema/CSGen.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4100,6 +4100,10 @@ bool ConstraintSystem::generateConstraints(
41004100
target.setExprConversionType(TypeChecker::getOptionalType(expr->getLoc(), var));
41014101
}
41024102

4103+
expr = buildTypeErasedExpr(expr, target.getDeclContext(),
4104+
target.getExprContextualType(),
4105+
target.getExprContextualTypePurpose());
4106+
41034107
// Generate constraints for the main system.
41044108
expr = generateConstraints(expr, target.getDeclContext());
41054109
if (!expr)

lib/Sema/ConstraintSystem.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4078,6 +4078,36 @@ Expr *ConstraintSystem::buildAutoClosureExpr(Expr *expr,
40784078
return result;
40794079
}
40804080

4081+
Expr *ConstraintSystem::buildTypeErasedExpr(Expr *expr, DeclContext *dc,
4082+
Type contextualType,
4083+
ContextualTypePurpose purpose) {
4084+
if (!(purpose == CTP_ReturnStmt || purpose == CTP_ReturnSingleExpr))
4085+
return expr;
4086+
4087+
auto *decl = dyn_cast_or_null<ValueDecl>(dc->getAsDecl());
4088+
if (!decl ||
4089+
!(decl->isDynamic() || decl->getDynamicallyReplacedDecl()))
4090+
return expr;
4091+
4092+
auto *opaque = contextualType->getAs<OpaqueTypeArchetypeType>();
4093+
if (!opaque)
4094+
return expr;
4095+
4096+
auto protocols = opaque->getConformsTo();
4097+
if (protocols.size() != 1)
4098+
return expr;
4099+
4100+
auto *attr = protocols.front()->getAttrs().getAttribute<TypeEraserAttr>();
4101+
if (!attr)
4102+
return expr;
4103+
4104+
auto typeEraser = attr->getTypeEraserLoc().getType();
4105+
auto &ctx = dc->getASTContext();
4106+
return CallExpr::createImplicit(ctx,
4107+
TypeExpr::createImplicit(typeEraser, ctx),
4108+
{expr}, {ctx.Id_erasing});
4109+
}
4110+
40814111
/// If an UnresolvedDotExpr, SubscriptMember, etc has been resolved by the
40824112
/// constraint system, return the decl that it references.
40834113
ValueDecl *ConstraintSystem::findResolvedMemberRef(ConstraintLocator *locator) {

lib/Sema/ConstraintSystem.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3626,6 +3626,21 @@ class ConstraintSystem {
36263626
/// Given expression represents computed result of the closure.
36273627
Expr *buildAutoClosureExpr(Expr *expr, FunctionType *closureType);
36283628

3629+
/// Builds a type-erased return expression that can be used in dynamic
3630+
/// replacement.
3631+
///
3632+
/// An expression needs type erasure if:
3633+
/// 1. The expression is a return value.
3634+
/// 2. The enclosing function is dynamic or a dynamic replacement.
3635+
/// 3. The enclosing function returns an opaque type.
3636+
/// 4. The opaque type conforms to (exactly) one protocol, and the protocol
3637+
/// has a declared type eraser.
3638+
///
3639+
/// \returns the transformed return expression, or the original expression if
3640+
/// no type erasure is needed.
3641+
Expr *buildTypeErasedExpr(Expr *expr, DeclContext *dc, Type contextualType,
3642+
ContextualTypePurpose purpose);
3643+
36293644
private:
36303645
/// Determines whether or not a given conversion at a given locator requires
36313646
/// the creation of a temporary value that's only valid for a limited scope.

lib/Serialization/Deserialization.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4208,6 +4208,19 @@ llvm::Error DeclDeserializer::deserializeDeclAttributes() {
42084208
break;
42094209
}
42104210

4211+
case decls_block::TypeEraser_DECL_ATTR: {
4212+
bool isImplicit;
4213+
TypeID typeEraserID;
4214+
serialization::decls_block::TypeEraserDeclAttrLayout::readRecord(
4215+
scratch, isImplicit, typeEraserID);
4216+
4217+
auto typeEraser = MF.getType(typeEraserID);
4218+
assert(!isImplicit);
4219+
Attr = new (ctx) TypeEraserAttr(SourceLoc(), SourceRange(),
4220+
TypeLoc::withoutLoc(typeEraser));
4221+
break;
4222+
}
4223+
42114224
case decls_block::Custom_DECL_ATTR: {
42124225
bool isImplicit;
42134226
TypeID typeID;

lib/Serialization/Serialization.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2173,7 +2173,6 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
21732173
case DAK_RestatedObjCConformance:
21742174
case DAK_ClangImporterSynthesizedType:
21752175
case DAK_PrivateImport:
2176-
case DAK_TypeEraser:
21772176
llvm_unreachable("cannot serialize attribute");
21782177

21792178
case DAK_Count:
@@ -2364,6 +2363,16 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
23642363
return;
23652364
}
23662365

2366+
case DAK_TypeEraser: {
2367+
auto abbrCode = S.DeclTypeAbbrCodes[TypeEraserDeclAttrLayout::Code];
2368+
auto attr = cast<TypeEraserAttr>(DA);
2369+
auto typeEraser = attr->getTypeEraserLoc().getType();
2370+
TypeEraserDeclAttrLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode,
2371+
attr->isImplicit(),
2372+
S.addTypeRef(typeEraser));
2373+
return;
2374+
}
2375+
23672376
case DAK_Custom: {
23682377
auto abbrCode = S.DeclTypeAbbrCodes[CustomDeclAttrLayout::Code];
23692378
auto theAttr = cast<CustomAttr>(DA);

test/Sema/type_eraser.swift

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// RUN: %target-swift-frontend -typecheck -disable-availability-checking -dump-ast %s | %FileCheck %s
2+
3+
class AnyP: P {
4+
init<T: P>(erasing: T) {}
5+
}
6+
7+
@_typeEraser(AnyP)
8+
protocol P {}
9+
10+
struct ConcreteP: P, Hashable {}
11+
12+
// CHECK-LABEL: testBasic
13+
dynamic func testBasic() -> some P {
14+
// CHECK: underlying_to_opaque_expr{{.*}}'some P'
15+
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
16+
// CHECK: call_expr type='ConcreteP'
17+
ConcreteP()
18+
}
19+
20+
// CHECK-LABEL: testTypeAlias
21+
typealias AliasForP = P
22+
dynamic func testTypeAlias() -> some AliasForP {
23+
// CHECK: underlying_to_opaque_expr{{.*}}'some P'
24+
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
25+
// CHECK: call_expr type='ConcreteP'
26+
ConcreteP()
27+
}
28+
29+
// CHECK-LABEL: testNoDynamic
30+
func testNoDynamic() -> some P {
31+
// CHECK: underlying_to_opaque_expr{{.*}}'some P'
32+
// CHECK-NEXT: call_expr type='ConcreteP'
33+
ConcreteP()
34+
}
35+
36+
// CHECK-LABEL: testNoOpaque
37+
dynamic func testNoOpaque() -> P {
38+
// CHECK: erasure_expr implicit type='P'
39+
// CHECK-NEXT: normal_conformance type=ConcreteP protocol=P
40+
// CHECK-NEXT: call_expr type='ConcreteP'
41+
ConcreteP()
42+
}
43+
44+
// CHECK-LABEL: testComposition
45+
typealias Composition = P & Hashable
46+
dynamic func testComposition() -> some Composition {
47+
// CHECK: underlying_to_opaque_expr{{.*}}'some Hashable & P'
48+
// CHECK-NEXT: call_expr type='ConcreteP'
49+
ConcreteP()
50+
}
51+
52+
// CHECK-LABEL: struct_decl{{.*}}Builder
53+
@_functionBuilder
54+
struct Builder {
55+
static func buildBlock(_ params: P...) -> ConcreteP {
56+
return ConcreteP()
57+
}
58+
}
59+
60+
// CHECK-LABEL: TestFunctionBuilder
61+
class TestFunctionBuilder {
62+
// CHECK-LABEL: testTransformFnBody
63+
@Builder dynamic var testTransformFnBody: some P {
64+
// CHECK: return_stmt
65+
// CHECK-NEXT: underlying_to_opaque_expr implicit type='some P'
66+
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
67+
// CHECK: declref_expr implicit type='@lvalue ConcreteP'
68+
ConcreteP()
69+
}
70+
71+
// CHECK-LABEL: func_decl{{.*}}takesBuilder
72+
func takesBuilder(@Builder closure: () -> ConcreteP) -> ConcreteP { closure() }
73+
74+
// CHECK-LABEL: testClosureBuilder
75+
dynamic var testClosureBuilder: some P {
76+
// CHECK: underlying_to_opaque_expr implicit type='some P'
77+
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
78+
// CHECK: closure_expr type='() -> ConcreteP'
79+
takesBuilder {
80+
// CHECK: return_stmt
81+
// CHECK-NEXT: load_expr implicit type='ConcreteP'
82+
ConcreteP()
83+
}
84+
}
85+
}
86+
87+
// CHECK-LABEL: class_decl{{.*}}DynamicReplacement
88+
class DynamicReplacement {
89+
dynamic func testDynamicReplaceable() -> some P {
90+
// CHECK: underlying_to_opaque_expr implicit type='some P'
91+
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
92+
// CHECK: call_expr type='ConcreteP'
93+
ConcreteP()
94+
}
95+
}
96+
97+
// CHECK-LABEL: extension_decl{{.*}}DynamicReplacement
98+
extension DynamicReplacement {
99+
// CHECK-LABEL: testDynamicReplacement
100+
@_dynamicReplacement(for: testDynamicReplaceable)
101+
func testDynamicReplacement() -> some P {
102+
print("not single expr return")
103+
// CHECK: return_stmt
104+
// CHECK-NEXT: underlying_to_opaque_expr implicit type='some P'
105+
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
106+
// CHECK: call_expr type='ConcreteP'
107+
return ConcreteP()
108+
}
109+
}

test/Serialization/serialize_attr.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,15 @@ public class CC<T : PP> {
8282
// CHECK-DAG: sil [serialized] [_specialize exported: false, kind: full, where T == Int, U == Float] [canonical] [ossa] @$s14serialize_attr14specializeThis_1uyx_q_tr0_lF : $@convention(thin) <T, U> (@in_guaranteed T, @in_guaranteed U) -> () {
8383

8484
// CHECK-DAG: sil [serialized] [noinline] [_specialize exported: false, kind: full, where T == RR, U == SS] [canonical] [ossa] @$s14serialize_attr2CCC3foo_1gqd___AA2GGVyxGtqd___AHtAA2QQRd__lF : $@convention(method) <T where T : PP><U where U : QQ> (@in_guaranteed U, GG<T>, @guaranteed CC<T>) -> (@out U, GG<T>) {
85+
86+
87+
// @_typeEraser
88+
// -----------------------------------------------------------------------------
89+
90+
public class AnyP: P {
91+
public init<T: P>(erasing: T) {}
92+
}
93+
94+
// CHECK-DAG: @_typeEraser(AnyP) protocol P
95+
@_typeEraser(AnyP)
96+
public protocol P {}

0 commit comments

Comments
 (0)