Skip to content

[Property Wrappers] Refactor property wrappers so the synthesized backing init is only type checked once #30807

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 14, 2020
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
2 changes: 1 addition & 1 deletion include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5235,7 +5235,7 @@ class VarDecl : public AbstractStorageDecl {
///
/// \code
/// @Lazy var i = 17
/// \end
/// \endcode
///
/// Or when there is no initializer but each composed property wrapper has
/// a suitable `init(wrappedValue:)`.
Expand Down
56 changes: 55 additions & 1 deletion include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -4041,7 +4041,61 @@ class OpaqueValueExpr : public Expr {
SourceRange getSourceRange() const { return Range; }

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::OpaqueValue;
return E->getKind() == ExprKind::OpaqueValue;
}
};

/// A placeholder to substitute with a \c wrappedValue initialization expression
/// for a property with an attached property wrapper.
///
/// Wrapped value placeholder expressions are injected around the
/// \c wrappedValue argument in a synthesized \c init(wrappedValue:)
/// call. This injection happens for properties with attached property wrappers
/// that can be initialized out-of-line with a wrapped value expression, rather
/// than calling \c init(wrappedValue:) explicitly.
///
/// Wrapped value placeholders store the original initialization expression
/// if one exists, along with an opaque value placeholder that can be bound
/// to a different wrapped value expression.
class PropertyWrapperValuePlaceholderExpr : public Expr {
SourceRange Range;
OpaqueValueExpr *Placeholder;
Expr *WrappedValue;

PropertyWrapperValuePlaceholderExpr(SourceRange Range, Type Ty,
OpaqueValueExpr *placeholder,
Expr *wrappedValue)
: Expr(ExprKind::PropertyWrapperValuePlaceholder, /*Implicit=*/true, Ty),
Range(Range), Placeholder(placeholder), WrappedValue(wrappedValue) {}

public:
static PropertyWrapperValuePlaceholderExpr *
create(ASTContext &ctx, SourceRange range, Type ty, Expr *wrappedValue);

/// The original wrappedValue initialization expression provided via
/// \c = on a proprety with attached property wrappers.
Expr *getOriginalWrappedValue() const {
return WrappedValue;
}

void setOriginalWrappedValue(Expr *value) {
WrappedValue = value;
}

/// An opaque value placeholder that will be used to substitute in a
/// different wrapped value expression for out-of-line initialization.
OpaqueValueExpr *getOpaqueValuePlaceholder() const {
return Placeholder;
}

void setOpaqueValuePlaceholder(OpaqueValueExpr *placeholder) {
Placeholder = placeholder;
}

SourceRange getSourceRange() const { return Range; }

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::PropertyWrapperValuePlaceholder;
}
};

Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/ExprNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ EXPR(VarargExpansion, Expr)
EXPR(DynamicType, Expr)
EXPR(RebindSelfInConstructor, Expr)
EXPR(OpaqueValue, Expr)
EXPR(PropertyWrapperValuePlaceholder, Expr)
EXPR(DefaultArgument, Expr)
EXPR(BindOptional, Expr)
EXPR(OptionalEvaluation, Expr)
Expand Down
13 changes: 7 additions & 6 deletions include/swift/AST/PropertyWrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ struct PropertyWrapperBackingPropertyInfo {
///
/// \code
/// @Lazy var i = 17
/// \end
/// \endcode
///
/// This is the specified initial value (\c 17), which is suitable for
/// embedding in the expression \c initializeFromOriginal.
Expand Down Expand Up @@ -190,12 +190,13 @@ void simple_display(
llvm::raw_ostream &out,
const PropertyWrapperBackingPropertyInfo &backingInfo);

/// Given the initializer for the given property with an attached property
/// wrapper, dig out the original initialization expression.
/// Given the initializer for a property with an attached property wrapper,
/// dig out the wrapped value placeholder for the original initialization
/// expression.
///
/// Cannot just dig out the getOriginalInit() value because this function checks
/// types, etc. Erroneous code won't return a result from here.
Expr *findOriginalPropertyWrapperInitialValue(VarDecl *var, Expr *init);
/// \note The wrapped value placeholder is injected for properties that can
/// be initialized out-of-line using an expression of the wrapped property type.
PropertyWrapperValuePlaceholderExpr *findWrappedValuePlaceholder(Expr *init);

} // end namespace swift

Expand Down
12 changes: 12 additions & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2501,6 +2501,18 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}

void visitPropertyWrapperValuePlaceholderExpr(
PropertyWrapperValuePlaceholderExpr *E) {
printCommon(E, "property_wrapper_value_placeholder_expr");
OS << '\n';
printRec(E->getOpaqueValuePlaceholder());
if (auto *value = E->getOriginalWrappedValue()) {
OS << '\n';
printRec(value);
}
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}

void visitDefaultArgumentExpr(DefaultArgumentExpr *E) {
printCommon(E, "default_argument_expr");
OS << " default_args_owner=";
Expand Down
17 changes: 17 additions & 0 deletions lib/AST/ASTWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,23 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,

Expr *visitOpaqueValueExpr(OpaqueValueExpr *E) { return E; }

Expr *visitPropertyWrapperValuePlaceholderExpr(
PropertyWrapperValuePlaceholderExpr *E) {
if (auto *placeholder = doIt(E->getOpaqueValuePlaceholder()))
E->setOpaqueValuePlaceholder(dyn_cast<OpaqueValueExpr>(placeholder));
else
return nullptr;

if (E->getOriginalWrappedValue()) {
if (auto *newValue = doIt(E->getOriginalWrappedValue()))
E->setOriginalWrappedValue(newValue);
else
return nullptr;
}

return E;
}

Expr *visitDefaultArgumentExpr(DefaultArgumentExpr *E) { return E; }

Expr *visitInterpolatedStringLiteralExpr(InterpolatedStringLiteralExpr *E) {
Expand Down
98 changes: 10 additions & 88 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6348,105 +6348,27 @@ void ParamDecl::setDefaultArgumentCaptureInfo(CaptureInfo captures) {
DefaultValueAndFlags.getPointer()->Captures = captures;
}

/// Return nullptr if there is no property wrapper
Expr *swift::findOriginalPropertyWrapperInitialValue(VarDecl *var,
Expr *init) {
auto *PBD = var->getParentPatternBinding();
if (!PBD)
return nullptr;

// If there is no '=' on the pattern, there was no initial value.
if (PBD->getEqualLoc(0).isInvalid() && !PBD->isDefaultInitializable())
return nullptr;

ASTContext &ctx = var->getASTContext();
auto dc = var->getInnermostDeclContext();
const auto wrapperAttrs = var->getAttachedPropertyWrappers();
if (wrapperAttrs.empty())
return nullptr;
auto innermostAttr = wrapperAttrs.back();
auto innermostNominal = evaluateOrDefault(
ctx.evaluator, CustomAttrNominalRequest{innermostAttr, dc}, nullptr);
if (!innermostNominal)
return nullptr;

// Walker
PropertyWrapperValuePlaceholderExpr *
swift::findWrappedValuePlaceholder(Expr *init) {
class Walker : public ASTWalker {
public:
NominalTypeDecl *innermostNominal;
Expr *initArg = nullptr;

Walker(NominalTypeDecl *innermostNominal)
: innermostNominal(innermostNominal) { }
PropertyWrapperValuePlaceholderExpr *placeholder = nullptr;

virtual std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
if (initArg)
if (placeholder)
return { false, E };

if (auto call = dyn_cast<CallExpr>(E)) {
ASTContext &ctx = innermostNominal->getASTContext();

// We're looking for an implicit call.
if (!call->isImplicit())
return { true, E };

// ... which may call the constructor of another property
// wrapper if there are multiple wrappers attached to the
// property.
if (auto tuple = dyn_cast<TupleExpr>(call->getArg())) {
if (tuple->getNumElements() > 0) {
for (unsigned i : range(tuple->getNumElements())) {
if (tuple->getElementName(i) == ctx.Id_wrappedValue ||
tuple->getElementName(i) == ctx.Id_initialValue) {
auto elem = tuple->getElement(i)->getSemanticsProvidingExpr();

// Look through autoclosures.
if (auto autoclosure = dyn_cast<AutoClosureExpr>(elem))
elem = autoclosure->getSingleExpressionBody();

if (elem->isImplicit() && isa<CallExpr>(elem)) {
return { true, E };
}
}
}
}
}

// ... producing a value of the same nominal type as the
// innermost property wrapper.
if (!call->getType() ||
call->getType()->getAnyNominal() != innermostNominal)
return { false, E };

// Find the implicit initialValue/wrappedValue argument.
if (auto tuple = dyn_cast<TupleExpr>(call->getArg())) {
for (unsigned i : range(tuple->getNumElements())) {
if (tuple->getElementName(i) == ctx.Id_wrappedValue ||
tuple->getElementName(i) == ctx.Id_initialValue) {
initArg = tuple->getElement(i);
return { false, E };
}
}
}
if (auto *value = dyn_cast<PropertyWrapperValuePlaceholderExpr>(E)) {
placeholder = value;
return { false, value };
}

return { true, E };
}
} walker(innermostNominal);
} walker;
init->walk(walker);

Expr *initArg = walker.initArg;
if (initArg) {
initArg = initArg->getSemanticsProvidingExpr();
if (auto autoclosure = dyn_cast<AutoClosureExpr>(initArg)) {
if (!var->isInnermostPropertyWrapperInitUsesEscapingAutoClosure()) {
// Remove the autoclosure part only for non-escaping autoclosures
initArg =
autoclosure->getSingleExpressionBody()->getSemanticsProvidingExpr();
}
}
}
return initArg;
return walker.placeholder;
}

/// Writes a tuple expression where each element is either `nil` or another such
Expand Down Expand Up @@ -6545,7 +6467,7 @@ ParamDecl::getDefaultValueStringRepresentation(
}

auto init =
findOriginalPropertyWrapperInitialValue(original, parentInit);
findWrappedValuePlaceholder(parentInit)->getOriginalWrappedValue();
return extractInlinableText(getASTContext().SourceMgr, init, scratch);
}
}
Expand Down
13 changes: 13 additions & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ ConcreteDeclRef Expr::getReferencedDecl(bool stopAtParenExpr) const {
PASS_THROUGH_REFERENCE(RebindSelfInConstructor, getSubExpr);

NO_REFERENCE(OpaqueValue);
NO_REFERENCE(PropertyWrapperValuePlaceholder);
NO_REFERENCE(DefaultArgument);

PASS_THROUGH_REFERENCE(BindOptional, getSubExpr);
Expand Down Expand Up @@ -624,6 +625,7 @@ bool Expr::canAppendPostfixExpression(bool appendingPostfixOperator) const {

case ExprKind::RebindSelfInConstructor:
case ExprKind::OpaqueValue:
case ExprKind::PropertyWrapperValuePlaceholder:
case ExprKind::DefaultArgument:
case ExprKind::BindOptional:
case ExprKind::OptionalEvaluation:
Expand Down Expand Up @@ -1403,6 +1405,17 @@ static ValueDecl *getCalledValue(Expr *E) {
return nullptr;
}

PropertyWrapperValuePlaceholderExpr *
PropertyWrapperValuePlaceholderExpr::create(ASTContext &ctx, SourceRange range,
Type ty, Expr *wrappedValue) {
auto *placeholder =
new (ctx) OpaqueValueExpr(range, ty, /*isPlaceholder=*/true);

return new (ctx) PropertyWrapperValuePlaceholderExpr(range, ty,
placeholder,
wrappedValue);
}

const ParamDecl *DefaultArgumentExpr::getParamDecl() const {
return getParameterAt(DefaultArgsOwner.getDecl(), ParamIndex);
}
Expand Down
43 changes: 34 additions & 9 deletions lib/SILGen/SILGenConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,21 @@ static void emitImplicitValueConstructor(SILGenFunction &SGF,

// Cleanup after this initialization.
FullExpr scope(SGF.Cleanups, field->getParentPatternBinding());

// If this is a property wrapper backing storage var that isn't
// memberwise initialized and has an original wrapped value, apply
// the property wrapper backing initializer.
if (auto *wrappedVar = field->getOriginalWrappedProperty()) {
auto wrappedInfo = wrappedVar->getPropertyWrapperBackingPropertyInfo();
if (wrappedInfo.originalInitialValue) {
auto arg = SGF.emitRValue(wrappedInfo.originalInitialValue);
maybeEmitPropertyWrapperInitFromValue(SGF, Loc, field, subs,
std::move(arg))
.forwardInto(SGF, Loc, init.get());
continue;
}
}

SGF.emitExprInto(field->getParentInitializer(), init.get());
}
}
Expand All @@ -235,27 +250,37 @@ static void emitImplicitValueConstructor(SILGenFunction &SGF,
for (VarDecl *field : decl->getStoredProperties()) {
auto fieldTy =
selfTy.getFieldType(field, SGF.SGM.M, SGF.getTypeExpansionContext());
SILValue v;
RValue value;

// If it's memberwise initialized, do so now.
if (field->isMemberwiseInitialized(/*preferDeclaredProperties=*/false)) {
FullExpr scope(SGF.Cleanups, field->getParentPatternBinding());
assert(elti != eltEnd && "number of args does not match number of fields");
(void)eltEnd;
v = maybeEmitPropertyWrapperInitFromValue(
SGF, Loc, field, subs, std::move(*elti))
.forwardAsSingleStorageValue(SGF, fieldTy, Loc);
value = std::move(*elti);
++elti;
} else {
// Otherwise, use its initializer.
assert(field->isParentInitialized());
Expr *init = field->getParentInitializer();

// If this is a property wrapper backing storage var that isn't
// memberwise initialized, use the original wrapped value if it exists.
if (auto *wrappedVar = field->getOriginalWrappedProperty()) {
auto wrappedInfo = wrappedVar->getPropertyWrapperBackingPropertyInfo();
if (wrappedInfo.originalInitialValue) {
init = wrappedInfo.originalInitialValue;
}
}

// Cleanup after this initialization.
FullExpr scope(SGF.Cleanups, field->getParentPatternBinding());
v = SGF.emitRValue(field->getParentInitializer())
.forwardAsSingleStorageValue(SGF, fieldTy, Loc);
value = SGF.emitRValue(init);
}

// Cleanup after this initialization.
FullExpr scope(SGF.Cleanups, field->getParentPatternBinding());
SILValue v = maybeEmitPropertyWrapperInitFromValue(SGF, Loc, field, subs,
std::move(value))
.forwardAsSingleStorageValue(SGF, fieldTy, Loc);

eltValues.push_back(v);
}

Expand Down
Loading