Skip to content
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

[WIP] Property behaviors #23362

Closed
wants to merge 3 commits into from
Closed
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
22 changes: 22 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4597,6 +4597,10 @@ class VarDecl : public AbstractStorageDecl {

Type typeInContext;

/// Property behavior information.
SourceLoc byLoc;
TypeLoc behaviorTypeLoc;

public:
VarDecl(bool IsStatic, Specifier Sp, bool IsCaptureList, SourceLoc NameLoc,
Identifier Name, DeclContext *DC)
Expand Down Expand Up @@ -4821,6 +4825,24 @@ class VarDecl : public AbstractStorageDecl {
Bits.VarDecl.IsREPLVar = IsREPLVar;
}

/// Whether this variable has a property behavior attached.
bool hasPropertyBehavior() const {
return !behaviorTypeLoc.isNull();
}

SourceLoc getPropertyBehaviorByLoc() const {
return byLoc;
}

TypeLoc &getPropertyBehaviorTypeLoc() {
return behaviorTypeLoc;
}

void setPropertyBehavior(SourceLoc byLoc, TypeLoc typeLoc) {
this->byLoc = byLoc;
this->behaviorTypeLoc = typeLoc;
}

/// Return the Objective-C runtime name for this property.
Identifier getObjCPropertyName() const;

Expand Down
8 changes: 8 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1620,6 +1620,14 @@ ERROR(pound_available_package_description_not_allowed, none,
ERROR(availability_query_repeated_platform, none,
"version for '%0' already specified", (StringRef))

//------------------------------------------------------------------------------
// MARK: property behavior diagnostics
//------------------------------------------------------------------------------
ERROR(property_behavior_not_named, none,
"property behavior can only by written on a single-variable pattern", ())
ERROR(expected_behavior_type_after_by,PointsToFirstBadToken,
"expected behavior type after 'by'", ())

//------------------------------------------------------------------------------
// MARK: syntax parsing diagnostics
//------------------------------------------------------------------------------
Expand Down
14 changes: 14 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4271,6 +4271,20 @@ WARNING(hashvalue_implementation,none,
"conform type %0 to 'Hashable' by implementing 'hash(into:)' instead",
(Type))

//------------------------------------------------------------------------------
// MARK: property behavior diagnostics
//------------------------------------------------------------------------------
ERROR(property_behavior_not_unbound, none,
"property behavior must name a generic type without any generic arguments", ())
ERROR(property_behavior_not_single_parameter, none,
"property behavior must refer to a single-parameter generic type", ())
ERROR(property_behavior_no_value_property, none,
"property behavior type %0 does not contain a non-static property "
"named 'value'", (Type))
ERROR(property_behavior_ambiguous_value_property, none,
"property behavior type %0 has multiple non-static properties "
"named 'value'", (Type))

#ifndef DIAG_NO_UNDEF
# if defined(DIAG)
# undef DIAG
Expand Down
24 changes: 22 additions & 2 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3330,7 +3330,8 @@ parseIdentifierDeclName(Parser &P, Identifier &Result, SourceLoc &Loc,
// We parsed an identifier for the declaration. If we see another
// identifier, it might've been a single identifier that got broken by a
// space or newline accidentally.
if (P.Tok.isIdentifierOrUnderscore() && !P.Tok.isContextualDeclKeyword())
if (P.Tok.isIdentifierOrUnderscore() && !P.Tok.isContextualDeclKeyword() &&
!P.Tok.isContextualKeyword("by"))
P.diagnoseConsecutiveIDs(Result.str(), Loc, DeclKindName);

// Return success anyway
Expand Down Expand Up @@ -5215,7 +5216,26 @@ Parser::parseDeclVar(ParseDeclOptions Flags,

pattern = patternRes.get();
}


// Parse a property behavior.
if (Tok.isContextualKeyword("by")) {
SourceLoc byLoc = consumeToken();
ParserResult<TypeRepr> behaviorType =
parseType(diag::expected_behavior_type_after_by);
if (behaviorType.hasCodeCompletion())
return makeResult(makeParserCodeCompletionStatus());

if (behaviorType.isNonNull()) {
if (auto var = pattern->getSingleVar()) {
var->setPropertyBehavior(byLoc, behaviorType.get());
} else {
// FIXME: Support AnyPattern as well, somehow.
diagnose(byLoc, diag::property_behavior_not_named)
.highlight(pattern->getSourceRange());
}
}
}

// Configure all vars with attributes, 'static' and parent pattern.
pattern->forEachVariable([&](VarDecl *VD) {
VD->setStatic(StaticLoc.isValid());
Expand Down
3 changes: 2 additions & 1 deletion lib/Parse/ParsePattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,8 @@ ParserResult<Pattern> Parser::parsePattern() {
PatternCtx.setCreateSyntax(SyntaxKind::IdentifierPattern);
Identifier name;
SourceLoc loc = consumeIdentifier(&name);
if (Tok.isIdentifierOrUnderscore() && !Tok.isContextualDeclKeyword())
if (Tok.isIdentifierOrUnderscore() && !Tok.isContextualDeclKeyword() &&
!Tok.isContextualKeyword("by"))
diagnoseConsecutiveIDs(name.str(), loc, isLet ? "constant" : "variable");

return makeParserResult(createBindingFromPattern(loc, name, specifier));
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ add_swift_host_library(swiftSema STATIC
TypeCheckGeneric.cpp
TypeCheckNameLookup.cpp
TypeCheckPattern.cpp
TypeCheckPropertyBehaviors.cpp
TypeCheckProtocol.cpp
TypeCheckProtocolInference.cpp
TypeCheckREPL.cpp
Expand Down
164 changes: 160 additions & 4 deletions lib/Sema/CodeSynthesis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "TypeChecker.h"
#include "TypeCheckObjC.h"
#include "TypeCheckType.h"
#include "TypeCheckPropertyBehaviors.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/Availability.h"
#include "swift/AST/Expr.h"
Expand Down Expand Up @@ -732,7 +733,9 @@ void createPropertyStoreOrCallSuperclassSetter(AccessorDecl *accessor,
LLVM_ATTRIBUTE_UNUSED
static bool isSynthesizedComputedProperty(AbstractStorageDecl *storage) {
return (storage->getAttrs().hasAttribute<LazyAttr>() ||
storage->getAttrs().hasAttribute<NSManagedAttr>());
storage->getAttrs().hasAttribute<NSManagedAttr>() ||
(isa<VarDecl>(storage) &&
cast<VarDecl>(storage)->hasPropertyBehavior()));
}

/// Synthesize the body of a trivial getter. For a non-member vardecl or one
Expand All @@ -743,8 +746,7 @@ static void synthesizeTrivialGetterBody(AccessorDecl *getter,
TargetImpl target,
ASTContext &ctx) {
auto storage = getter->getStorage();
assert(!storage->getAttrs().hasAttribute<LazyAttr>() &&
!storage->getAttrs().hasAttribute<NSManagedAttr>());
assert(!isSynthesizedComputedProperty(storage));

SourceLoc loc = storage->getLoc();

Expand Down Expand Up @@ -792,6 +794,26 @@ static void synthesizeReadCoroutineGetterBody(AccessorDecl *getter,
synthesizeTrivialGetterBody(getter, TargetImpl::Implementation, ctx);
}

/// Synthesize the body of a getter for a property behavior, which
/// delegates to the behavior's unwrap property.
static void synthesizePropertyBehaviorGetterBody(AccessorDecl *getter,
ASTContext &ctx) {
auto var = cast<VarDecl>(getter->getStorage());
auto backingVar = getOrSynthesizePropertyBehaviorBackingProperty(var);
auto unwrapVar = getPropertyBehaviorUnwrapProperty(var);

if (!backingVar || !unwrapVar)
return;

auto backingValue = createPropertyLoadOrCallSuperclassGetter(
getter, backingVar, TargetImpl::Storage, ctx);
auto unwrapRef = new (ctx) MemberRefExpr(
backingValue, SourceLoc(), unwrapVar, DeclNameLoc(), /*Implicit=*/true);
auto returnStmt = new (ctx) ReturnStmt(SourceLoc(), unwrapRef);
getter->setBody(
BraceStmt::create(ctx, SourceLoc(), { returnStmt }, SourceLoc()));
}

/// Synthesize the body of a setter which just stores to the given storage
/// declaration (which doesn't have to be the storage for the setter).
static void
Expand Down Expand Up @@ -822,6 +844,31 @@ static void synthesizeTrivialSetterBody(AccessorDecl *setter,
storage, ctx);
}

/// Synthesize the body of a setter for a property behavior, which
/// delegates to the behavior's unwrap property.
static void synthesizePropertyBehaviorSetterBody(AccessorDecl *setter,
ASTContext &ctx) {
auto var = cast<VarDecl>(setter->getStorage());
auto backingVar = getOrSynthesizePropertyBehaviorBackingProperty(var);
auto unwrapVar = getPropertyBehaviorUnwrapProperty(var);

if (!backingVar || !unwrapVar)
return;

VarDecl *newValueParamDecl = getFirstParamDecl(setter);
auto *newValueDRE =
new (ctx) DeclRefExpr(newValueParamDecl, DeclNameLoc(), IsImplicit);

auto backingValue =
buildStorageReference(setter, backingVar, TargetImpl::Storage, ctx);
auto unwrapRef = new (ctx) MemberRefExpr(
backingValue, SourceLoc(), unwrapVar, DeclNameLoc(), /*Implicit=*/true);
auto assign =
new (ctx) AssignExpr(unwrapRef, SourceLoc(), newValueDRE, IsImplicit);
setter->setBody(
BraceStmt::create(ctx, SourceLoc(), { assign }, SourceLoc()));
}

static void synthesizeCoroutineAccessorBody(AccessorDecl *accessor,
ASTContext &ctx) {
assert(accessor->isCoroutine());
Expand Down Expand Up @@ -1386,6 +1433,61 @@ void swift::completeLazyVarImplementation(VarDecl *VD) {
Storage->overwriteSetterAccess(AccessLevel::Private);
}

VarDecl *swift::getOrSynthesizePropertyBehaviorBackingProperty(VarDecl *var) {
// FIXME: Didn't want to recompile everything just yet. Don't merge this :)
static llvm::DenseMap<VarDecl *, VarDecl *> backingVars;
VarDecl *backingVar = backingVars[var];
if (backingVar) return backingVar;

ASTContext &ctx = var->getASTContext();

// Compute the name of the storage type.
SmallString<64> nameBuf;
nameBuf = "$";
nameBuf += var->getName().str();
Identifier name = ctx.getIdentifier(nameBuf);

// Determine the type of the storage.
auto dc = var->getDeclContext();
Type storageInterfaceType =
applyPropertyBehaviorType(var->getInterfaceType(), var,
TypeResolution::forInterface(dc));
Type storageType = dc->mapTypeIntoContext(storageInterfaceType);

// Create the backing storage property and note it in the cache.
backingVar = new (ctx) VarDecl(/*IsStatic=*/var->isStatic(),
VarDecl::Specifier::Var,
/*IsCaptureList=*/false,
var->getPropertyBehaviorByLoc(),
name, dc);
backingVars[var] = backingVar;

backingVar->setInterfaceType(storageInterfaceType);
backingVar->setType(storageType);
backingVar->setImplicit();
addMemberToContextIfNeeded(backingVar, dc, var);

// Create the pattern binding declaration for the backing property.
// FIXME: If the original property had an initializer, we'll have an
// initializer.
Pattern *pbdPattern = new (ctx) NamedPattern(backingVar, /*implicit=*/true);
pbdPattern = TypedPattern::createImplicit(ctx, pbdPattern, storageType);
auto pbd = PatternBindingDecl::createImplicit(
ctx, backingVar->getCorrectStaticSpelling(), pbdPattern,
/*init*/nullptr, dc, var->getPropertyBehaviorByLoc());
addMemberToContextIfNeeded(pbd, dc, var);

// Mark the backing property as 'final'. There's no sensible way to override.
if (dc->getSelfClassDecl())
makeFinal(ctx, backingVar);

// FIXME: Extra syntax could escalate the access level.
backingVar->overwriteAccess(AccessLevel::Private);
backingVar->overwriteSetterAccess(AccessLevel::Private);

return backingVar;
}

static bool wouldBeCircularSynthesis(AbstractStorageDecl *storage,
AccessorKind kind) {
switch (kind) {
Expand Down Expand Up @@ -1463,6 +1565,32 @@ static void maybeAddAccessorsToLazyVariable(VarDecl *var, ASTContext &ctx) {
addExpectedOpaqueAccessorsToStorage(var, ctx);
}

static void maybeAddAccessorsForPropertyBehavior(VarDecl *var,
ASTContext &ctx) {
auto behaviorVar = getPropertyBehaviorUnwrapProperty(var);
if (!behaviorVar)
return;

// If there are already accessors, something is invalid; bail out.
if (!var->getImplInfo().isSimpleStored())
return;

if (!var->getGetter()) {
addGetterToStorage(var, ctx);
}

if (!var->getSetter() && behaviorVar->isSettable(nullptr)) {
addSetterToStorage(var, ctx);
}

if (behaviorVar->isSettable(nullptr))
var->overwriteImplInfo(StorageImplInfo::getMutableComputed());
else
var->overwriteImplInfo(StorageImplInfo::getImmutableComputed());

addExpectedOpaqueAccessorsToStorage(var, ctx);
}

/// Try to add the appropriate accessors required a storage declaration.
/// This needs to be idempotent.
///
Expand All @@ -1478,6 +1606,14 @@ void swift::maybeAddAccessorsToStorage(AbstractStorageDecl *storage) {
return;
}

// Property behaviors require backing storage.
if (auto var = dyn_cast<VarDecl>(storage)) {
if (var->hasPropertyBehavior()) {
maybeAddAccessorsForPropertyBehavior(var, ctx);
return;
}
}

auto *dc = storage->getDeclContext();

// Local variables don't otherwise get accessors.
Expand Down Expand Up @@ -1555,6 +1691,16 @@ void swift::maybeAddAccessorsToStorage(AbstractStorageDecl *storage) {

static void synthesizeGetterBody(AccessorDecl *getter,
ASTContext &ctx) {
auto storage = getter->getStorage();

// Synthesize the getter for a property behavior.
if (auto var = dyn_cast<VarDecl>(storage)) {
if (var->hasPropertyBehavior()) {
synthesizePropertyBehaviorGetterBody(getter, ctx);
return;
}
}

if (getter->hasForcedStaticDispatch()) {
synthesizeTrivialGetterBody(getter, TargetImpl::Ordinary, ctx);
return;
Expand Down Expand Up @@ -1585,7 +1731,17 @@ static void synthesizeGetterBody(AccessorDecl *getter,

static void synthesizeSetterBody(AccessorDecl *setter,
ASTContext &ctx) {
switch (setter->getStorage()->getWriteImpl()) {
auto storage = setter->getStorage();

// Synthesize the setter for a property behavior.
if (auto var = dyn_cast<VarDecl>(storage)) {
if (var->hasPropertyBehavior()) {
synthesizePropertyBehaviorSetterBody(setter, ctx);
return;
}
}

switch (storage->getWriteImpl()) {
case WriteImplKind::Immutable:
llvm_unreachable("synthesizing setter from immutable storage");

Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/CodeSynthesis.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ void triggerAccessorSynthesis(TypeChecker &TC, AbstractStorageDecl *storage);
/// which must be lazy.
void completeLazyVarImplementation(VarDecl *lazyVar);

/// Retrieve or synthesize the backing property for the given property
/// behavior.
VarDecl *getOrSynthesizePropertyBehaviorBackingProperty(VarDecl *var);

/// Describes the kind of implicit constructor that will be
/// generated.
enum class ImplicitConstructorKind {
Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2418,6 +2418,10 @@ void TypeChecker::checkTypeModifyingDeclAttributes(VarDecl *var) {

if (auto *attr = var->getAttrs().getAttribute<ReferenceOwnershipAttr>())
checkReferenceOwnershipAttr(var, attr);

if (var->hasPropertyBehavior()) {

}
}

void TypeChecker::checkReferenceOwnershipAttr(VarDecl *var,
Expand Down
Loading