Skip to content

Commit

Permalink
Merge pull request swiftlang#30101 from hborla/dynamic-replacement-ty…
Browse files Browse the repository at this point in the history
…pe-erasure

[Sema] Implement type erasure for dynamic replacement.
  • Loading branch information
hborla authored Feb 28, 2020
2 parents 7fc6c13 + 5ce5096 commit 87bb775
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 2 deletions.
2 changes: 1 addition & 1 deletion include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ SIMPLE_DECL_ATTR(_inheritsConvenienceInitializers,
93)

DECL_ATTR(_typeEraser, TypeEraser,
OnProtocol | UserInaccessible | NotSerialized |
OnProtocol | UserInaccessible |
ABIStableToAdd | ABIBreakingToRemove | APIStableToAdd | APIBreakingToRemove,
94)

Expand Down
14 changes: 14 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,20 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
break;
}

case DAK_TypeEraser: {
Printer.printAttrName("@_typeEraser");
Printer << "(";
Printer.callPrintNamePre(PrintNameContext::Attribute);
auto typeLoc = cast<TypeEraserAttr>(this)->getTypeEraserLoc();
if (auto type = typeLoc.getType())
type->print(Printer, Options);
else
typeLoc.getTypeRepr()->print(Printer, Options);
Printer.printNamePost(PrintNameContext::Attribute);
Printer << ")";
break;
}

case DAK_Custom: {
Printer.callPrintNamePre(PrintNameContext::Attribute);
Printer << "@";
Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/BuilderTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ class BuilderClosureVisitor
return None;

applied.returnExpr = buildVarRef(bodyVar, stmt->getEndLoc());
applied.returnExpr = cs->buildTypeErasedExpr(applied.returnExpr,
dc, applied.bodyResultType,
CTP_ReturnStmt);

applied.returnExpr = cs->generateConstraints(applied.returnExpr, dc);
if (!applied.returnExpr) {
hadError = true;
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7498,6 +7498,7 @@ ExprWalker::rewriteTarget(SolutionApplicationTarget target) {
return None;

result.setFunctionBody(newBody);
fn.getAbstractFunctionDecl()->setHasSingleExpressionBody(false);
}

// Follow-up tasks.
Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/CSGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4100,6 +4100,10 @@ bool ConstraintSystem::generateConstraints(
target.setExprConversionType(TypeChecker::getOptionalType(expr->getLoc(), var));
}

expr = buildTypeErasedExpr(expr, target.getDeclContext(),
target.getExprContextualType(),
target.getExprContextualTypePurpose());

// Generate constraints for the main system.
expr = generateConstraints(expr, target.getDeclContext());
if (!expr)
Expand Down
30 changes: 30 additions & 0 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4078,6 +4078,36 @@ Expr *ConstraintSystem::buildAutoClosureExpr(Expr *expr,
return result;
}

Expr *ConstraintSystem::buildTypeErasedExpr(Expr *expr, DeclContext *dc,
Type contextualType,
ContextualTypePurpose purpose) {
if (!(purpose == CTP_ReturnStmt || purpose == CTP_ReturnSingleExpr))
return expr;

auto *decl = dyn_cast_or_null<ValueDecl>(dc->getAsDecl());
if (!decl ||
!(decl->isDynamic() || decl->getDynamicallyReplacedDecl()))
return expr;

auto *opaque = contextualType->getAs<OpaqueTypeArchetypeType>();
if (!opaque)
return expr;

auto protocols = opaque->getConformsTo();
if (protocols.size() != 1)
return expr;

auto *attr = protocols.front()->getAttrs().getAttribute<TypeEraserAttr>();
if (!attr)
return expr;

auto typeEraser = attr->getTypeEraserLoc().getType();
auto &ctx = dc->getASTContext();
return CallExpr::createImplicit(ctx,
TypeExpr::createImplicit(typeEraser, ctx),
{expr}, {ctx.Id_erasing});
}

/// If an UnresolvedDotExpr, SubscriptMember, etc has been resolved by the
/// constraint system, return the decl that it references.
ValueDecl *ConstraintSystem::findResolvedMemberRef(ConstraintLocator *locator) {
Expand Down
15 changes: 15 additions & 0 deletions lib/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -3626,6 +3626,21 @@ class ConstraintSystem {
/// Given expression represents computed result of the closure.
Expr *buildAutoClosureExpr(Expr *expr, FunctionType *closureType);

/// Builds a type-erased return expression that can be used in dynamic
/// replacement.
///
/// An expression needs type erasure if:
/// 1. The expression is a return value.
/// 2. The enclosing function is dynamic or a dynamic replacement.
/// 3. The enclosing function returns an opaque type.
/// 4. The opaque type conforms to (exactly) one protocol, and the protocol
/// has a declared type eraser.
///
/// \returns the transformed return expression, or the original expression if
/// no type erasure is needed.
Expr *buildTypeErasedExpr(Expr *expr, DeclContext *dc, Type contextualType,
ContextualTypePurpose purpose);

private:
/// Determines whether or not a given conversion at a given locator requires
/// the creation of a temporary value that's only valid for a limited scope.
Expand Down
13 changes: 13 additions & 0 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4208,6 +4208,19 @@ llvm::Error DeclDeserializer::deserializeDeclAttributes() {
break;
}

case decls_block::TypeEraser_DECL_ATTR: {
bool isImplicit;
TypeID typeEraserID;
serialization::decls_block::TypeEraserDeclAttrLayout::readRecord(
scratch, isImplicit, typeEraserID);

auto typeEraser = MF.getType(typeEraserID);
assert(!isImplicit);
Attr = new (ctx) TypeEraserAttr(SourceLoc(), SourceRange(),
TypeLoc::withoutLoc(typeEraser));
break;
}

case decls_block::Custom_DECL_ATTR: {
bool isImplicit;
TypeID typeID;
Expand Down
11 changes: 10 additions & 1 deletion lib/Serialization/Serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2173,7 +2173,6 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
case DAK_RestatedObjCConformance:
case DAK_ClangImporterSynthesizedType:
case DAK_PrivateImport:
case DAK_TypeEraser:
llvm_unreachable("cannot serialize attribute");

case DAK_Count:
Expand Down Expand Up @@ -2364,6 +2363,16 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
return;
}

case DAK_TypeEraser: {
auto abbrCode = S.DeclTypeAbbrCodes[TypeEraserDeclAttrLayout::Code];
auto attr = cast<TypeEraserAttr>(DA);
auto typeEraser = attr->getTypeEraserLoc().getType();
TypeEraserDeclAttrLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode,
attr->isImplicit(),
S.addTypeRef(typeEraser));
return;
}

case DAK_Custom: {
auto abbrCode = S.DeclTypeAbbrCodes[CustomDeclAttrLayout::Code];
auto theAttr = cast<CustomAttr>(DA);
Expand Down
109 changes: 109 additions & 0 deletions test/Sema/type_eraser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// RUN: %target-swift-frontend -typecheck -disable-availability-checking -dump-ast %s | %FileCheck %s

class AnyP: P {
init<T: P>(erasing: T) {}
}

@_typeEraser(AnyP)
protocol P {}

struct ConcreteP: P, Hashable {}

// CHECK-LABEL: testBasic
dynamic func testBasic() -> some P {
// CHECK: underlying_to_opaque_expr{{.*}}'some P'
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
// CHECK: call_expr type='ConcreteP'
ConcreteP()
}

// CHECK-LABEL: testTypeAlias
typealias AliasForP = P
dynamic func testTypeAlias() -> some AliasForP {
// CHECK: underlying_to_opaque_expr{{.*}}'some P'
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
// CHECK: call_expr type='ConcreteP'
ConcreteP()
}

// CHECK-LABEL: testNoDynamic
func testNoDynamic() -> some P {
// CHECK: underlying_to_opaque_expr{{.*}}'some P'
// CHECK-NEXT: call_expr type='ConcreteP'
ConcreteP()
}

// CHECK-LABEL: testNoOpaque
dynamic func testNoOpaque() -> P {
// CHECK: erasure_expr implicit type='P'
// CHECK-NEXT: normal_conformance type=ConcreteP protocol=P
// CHECK-NEXT: call_expr type='ConcreteP'
ConcreteP()
}

// CHECK-LABEL: testComposition
typealias Composition = P & Hashable
dynamic func testComposition() -> some Composition {
// CHECK: underlying_to_opaque_expr{{.*}}'some Hashable & P'
// CHECK-NEXT: call_expr type='ConcreteP'
ConcreteP()
}

// CHECK-LABEL: struct_decl{{.*}}Builder
@_functionBuilder
struct Builder {
static func buildBlock(_ params: P...) -> ConcreteP {
return ConcreteP()
}
}

// CHECK-LABEL: TestFunctionBuilder
class TestFunctionBuilder {
// CHECK-LABEL: testTransformFnBody
@Builder dynamic var testTransformFnBody: some P {
// CHECK: return_stmt
// CHECK-NEXT: underlying_to_opaque_expr implicit type='some P'
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
// CHECK: declref_expr implicit type='@lvalue ConcreteP'
ConcreteP()
}

// CHECK-LABEL: func_decl{{.*}}takesBuilder
func takesBuilder(@Builder closure: () -> ConcreteP) -> ConcreteP { closure() }

// CHECK-LABEL: testClosureBuilder
dynamic var testClosureBuilder: some P {
// CHECK: underlying_to_opaque_expr implicit type='some P'
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
// CHECK: closure_expr type='() -> ConcreteP'
takesBuilder {
// CHECK: return_stmt
// CHECK-NEXT: load_expr implicit type='ConcreteP'
ConcreteP()
}
}
}

// CHECK-LABEL: class_decl{{.*}}DynamicReplacement
class DynamicReplacement {
dynamic func testDynamicReplaceable() -> some P {
// CHECK: underlying_to_opaque_expr implicit type='some P'
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
// CHECK: call_expr type='ConcreteP'
ConcreteP()
}
}

// CHECK-LABEL: extension_decl{{.*}}DynamicReplacement
extension DynamicReplacement {
// CHECK-LABEL: testDynamicReplacement
@_dynamicReplacement(for: testDynamicReplaceable)
func testDynamicReplacement() -> some P {
print("not single expr return")
// CHECK: return_stmt
// CHECK-NEXT: underlying_to_opaque_expr implicit type='some P'
// CHECK-NEXT: call_expr implicit type='AnyP'{{.*}}arg_labels=erasing:
// CHECK: call_expr type='ConcreteP'
return ConcreteP()
}
}
12 changes: 12 additions & 0 deletions test/Serialization/serialize_attr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,15 @@ public class CC<T : PP> {
// 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) -> () {

// 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>) {


// @_typeEraser
// -----------------------------------------------------------------------------

public class AnyP: P {
public init<T: P>(erasing: T) {}
}

// CHECK-DAG: @_typeEraser(AnyP) protocol P
@_typeEraser(AnyP)
public protocol P {}

0 comments on commit 87bb775

Please sign in to comment.