Skip to content

[ParseableInterface] Standardize printing for accessors #20250

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
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/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 468; // Last change: SelfProtocolConformance
const uint16_t SWIFTMODULE_VERSION_MINOR = 469; // @_hasStorage

using DeclIDField = BCFixed<31>;

Expand Down
59 changes: 42 additions & 17 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -591,11 +591,13 @@ class PrintAST : public ASTVisitor<PrintAST> {
return;

printAccess(D->getFormalAccess());
bool shouldSkipSetterAccess =
llvm::is_contained(Options.ExcludeAttrList, DAK_SetterAccess);

if (auto storageDecl = dyn_cast<AbstractStorageDecl>(D)) {
if (auto setter = storageDecl->getSetter()) {
AccessLevel setterAccess = setter->getFormalAccess();
if (setterAccess != D->getFormalAccess())
if (setterAccess != D->getFormalAccess() && !shouldSkipSetterAccess)
printAccess(setterAccess, "(set)");
}
}
Expand Down Expand Up @@ -865,6 +867,10 @@ static bool hasNonMutatingSetter(const AbstractStorageDecl *ASD) {
return setter && setter->isExplicitNonMutating();
}

static bool hasLessAccessibleSetter(const AbstractStorageDecl *ASD) {
return ASD->getSetterFormalAccess() < ASD->getFormalAccess();
}

void PrintAST::printAttributes(const Decl *D) {
if (Options.SkipAttributes)
return;
Expand All @@ -880,11 +886,20 @@ void PrintAST::printAttributes(const Decl *D) {
Options.ExcludeAttrList.push_back(DAK_Final);
}

// Don't print @_hasInitialValue if we're printing an initializer
// expression.
if (auto vd = dyn_cast<VarDecl>(D)) {
// Don't print @_hasInitialValue if we're printing an initializer
// expression.
if (vd->isInitExposedToClients())
Options.ExcludeAttrList.push_back(DAK_HasInitialValue);

if (!Options.PrintForSIL) {
// Don't print @_hasStorage if the value is simply stored, or the
// decl is resilient.
if (vd->isResilient() ||
(vd->getImplInfo().isSimpleStored() &&
!hasLessAccessibleSetter(vd)))
Options.ExcludeAttrList.push_back(DAK_HasStorage);
}
}

// Don't print any contextual decl modifiers.
Expand Down Expand Up @@ -1613,19 +1628,28 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) {
if (isa<VarDecl>(ASD) && !Options.PrintPropertyAccessors)
return;

// Never print anything for stored properties.
if (ASD->getAllAccessors().empty())
return;

auto impl = ASD->getImplInfo();

// Treat StoredWithTrivialAccessors the same as Stored unless
// we're printing for SIL, in which case we want to distinguish it
// from a pure stored property.
// Don't print accessors for trivially stored properties...
if (impl.isSimpleStored()) {
// ...unless we're printing for SIL, which expects a { get set? } on
// trivial properties
if (Options.PrintForSIL) {
Printer << " { get " << (impl.supportsMutation() ? "set }" : "}");
}
// ...or you're private/internal(set), at which point we'll print
// @_hasStorage var x: T { get }
else if (ASD->isSettable(nullptr) && hasLessAccessibleSetter(ASD)) {
Printer << " {";
{
IndentRAII indentMore(*this);
indent();
Printer.printNewline();
Printer << "get";
Printer.printNewline();
}
Printer << "}";
}
return;
}

Expand Down Expand Up @@ -1762,12 +1786,8 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) {
llvm_unreachable("simply-stored variable should have been filtered out");
case WriteImplKind::StoredWithObservers:
case WriteImplKind::InheritedWithObservers: {
bool skippedWillSet = PrintAccessor(ASD->getWillSetFunc());
bool skippedDidSet = PrintAccessor(ASD->getDidSetFunc());
if (skippedDidSet && skippedWillSet) {
PrintAccessor(ASD->getGetter());
PrintAccessor(ASD->getSetter());
}
PrintAccessor(ASD->getGetter());
PrintAccessor(ASD->getSetter());
break;
}
case WriteImplKind::Set:
Expand Down Expand Up @@ -2091,6 +2111,12 @@ void PrintAST::visitPatternBindingDecl(PatternBindingDecl *decl) {
SmallString<128> scratch;
Printer << " = " << entry.getInitStringRepresentation(scratch);
}

// HACK: If we're just printing a single pattern and it has accessors,
// print the accessors here.
if (decl->getNumPatternEntries() == 1) {
printAccessors(vd);
}
}
}

Expand Down Expand Up @@ -2322,7 +2348,6 @@ void PrintAST::visitProtocolDecl(ProtocolDecl *decl) {
Options.BracketOptions.shouldCloseNominal(decl));
}
}

static bool isStructOrClassContext(DeclContext *dc) {
auto *nominal = dc->getSelfNominalTypeDecl();
if (nominal == nullptr)
Expand Down
86 changes: 84 additions & 2 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4777,6 +4777,72 @@ static void diagnoseAndIgnoreObservers(Parser &P,
}
}

/// Gets the storage info of the provided storage decl if it has the
/// @_hasStorage attribute and it's not in SIL mode.
///
/// In this case, we say the decl is:
///
/// Read:
/// - Stored, always
/// Write:
/// - Stored, if the decl is a 'var'.
/// - StoredWithObservers, if the decl has a setter
/// - This indicates that the original decl had a 'didSet' and/or 'willSet'
/// - InheritedWithObservers, if the decl has a setter and is an overridde.
/// - Immutable, if the decl is a 'let' or it does not have a setter.
/// ReadWrite:
/// - Stored, if the decl has no accessors listed.
/// - Immutable, if the decl is a 'let' or it does not have a setter.
/// - MaterializeToTemporary, if the decl has a setter.
static StorageImplInfo classifyWithHasStorageAttr(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rjmccall This patch introduces a @_hasStorage attribute that allows us to break an ambiguity in parseable interfaces.

The mapping we've settled on is an understanding that

public private(set) var x: Int

maps to

@_hasStorage public var x: Int { get }

and

public var x: Int {
  willSet/didSet {}
}

maps to

@_hasStorage public var x: Int { get set }

With that in mind, is this mapping correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to rename the @_sil_stored attribute for this purpose.

If you're changing this, could you change it to list/expect all the public accessors, i.e. including modify and (eventually) _read? You might need to (1) move the logic for deciding the opaque accessors into Sema and then (2) change StorageImplInfo to store that bitset.

Copy link
Contributor Author

@harlanhaskins harlanhaskins Nov 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to handle _modify and _read if @_hasStorage is provided. Should they diagnose?

What are the semantics of @sil_stored var x: T { get set } vs. just @sil_stored var x: T? Or for @sil_stored let x: T { get } vs. just @sil_stored let x: T?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it declares that the accessors are actually available. I'm not sure that's important to know in SIL. I do think there's a strong argument for it being useful to unambiguously list out the available opaque accessors, but it should be the full set, not just {get set} and then inferring modify and read and so on. However, to express that properly, you might have to change the representation a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to do this in a future patch, because testing parseable interfaces is currently blocked on this representational change. For now, there's a loud FIXME explaining how it should eventually work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

Parser::ParsedAccessors &accessors, ASTContext &ctx,
AbstractStorageDecl *storage, const DeclAttributes &attrs) {

// Determines if the storage is immutable, either by declaring itself as a
// `let` or by omitting a setter.
auto isImmutable = [&]() {
if (auto varDecl = dyn_cast<VarDecl>(storage))
return varDecl->isImmutable();
if (accessors.Set == nullptr) return true;
return false;
};

// Determines if the storage had a private setter, i.e. it's not a 'let' and
// it had a setter.
auto isPrivateSet = [&]() {
if (auto varDecl = dyn_cast<VarDecl>(storage))
return !varDecl->isImmutable() && accessors.Set == nullptr;
return false;
};

// Default to stored writes.
WriteImplKind writeImpl = WriteImplKind::Stored;
ReadWriteImplKind readWriteImpl = ReadWriteImplKind::Stored;

if (accessors.Get && accessors.Set) {
// If we see `@_hasStorage var x: T { get set }`, then our property has
// willSet/didSet observers.
writeImpl = attrs.hasAttribute<OverrideAttr>() ?
WriteImplKind::InheritedWithObservers :
WriteImplKind::StoredWithObservers;
readWriteImpl = ReadWriteImplKind::MaterializeToTemporary;
} else if (isImmutable()) {
writeImpl = WriteImplKind::Immutable;
readWriteImpl = ReadWriteImplKind::Immutable;
}

if (isPrivateSet()) {
// If we saw a 'var' with no setter, that means it was
// private/internal(set). Honor that with a synthesized attribute.
storage->getAttrs().add(
new (ctx) SetterAccessAttr(
SourceLoc(), SourceLoc(), AccessLevel::Private, /*implicit: */true));
}

// Always force Stored reads if @_hasStorage is present.
return StorageImplInfo(ReadImplKind::Stored, writeImpl, readWriteImpl);
}

StorageImplInfo
Parser::ParsedAccessors::classify(Parser &P, AbstractStorageDecl *storage,
bool invalid, ParseDeclOptions flags,
Expand Down Expand Up @@ -4935,10 +5001,26 @@ Parser::ParsedAccessors::classify(Parser &P, AbstractStorageDecl *storage,
readWriteImpl = ReadWriteImplKind::Immutable;
}

// Allow the _hasStorage attribute to override all the accessors we parsed
// Allow the @_hasStorage attribute to override all the accessors we parsed
// when making the final classification.
if (attrs.hasAttribute<HasStorageAttr>()) {
return StorageImplInfo::getSimpleStored(StorageIsMutable_t(Set != nullptr));
// The SIL rules for @_hasStorage are slightly different from the non-SIL
// rules. In SIL mode, @_hasStorage marks that the type is simply stored,
// and the only thing that determines mutability is the existence of the
// setter.
//
// FIXME: SIL should not be special cased here. The behavior should be
// consistent between SIL and non-SIL.
// The strategy here should be to keep track of all opaque accessors
// along with enough information to access the storage trivially
// if allowed. This could be a representational change to
// StorageImplInfo such that it keeps a bitset of listed accessors
// and dynamically determines the access strategy from that.
if (P.isInSILMode())
return StorageImplInfo::getSimpleStored(
StorageIsMutable_t(Set != nullptr));

return classifyWithHasStorageAttr(*this, P.Context, storage, attrs);
}

return StorageImplInfo(readImpl, writeImpl, readWriteImpl);
Expand Down
12 changes: 1 addition & 11 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class AttributeEarlyChecker : public AttributeVisitor<AttributeEarlyChecker> {
IGNORED_ATTR(FixedLayout)
IGNORED_ATTR(ForbidSerializingReference)
IGNORED_ATTR(Frozen)
IGNORED_ATTR(HasStorage)
IGNORED_ATTR(Implements)
IGNORED_ATTR(ImplicitlyUnwrappedOptional)
IGNORED_ATTR(Infix)
Expand Down Expand Up @@ -285,7 +286,6 @@ class AttributeEarlyChecker : public AttributeVisitor<AttributeEarlyChecker> {
void visitAccessControlAttr(AccessControlAttr *attr);
void visitSetterAccessAttr(SetterAccessAttr *attr);
bool visitAbstractAccessControlAttr(AbstractAccessControlAttr *attr);
void visitHasStorageAttr(HasStorageAttr *attr);
void visitObjCMembersAttr(ObjCMembersAttr *attr);
};
} // end anonymous namespace
Expand Down Expand Up @@ -428,16 +428,6 @@ void AttributeEarlyChecker::visitGKInspectableAttr(GKInspectableAttr *attr) {
attr->getAttrName());
}

void AttributeEarlyChecker::visitHasStorageAttr(HasStorageAttr *attr) {
auto *VD = cast<VarDecl>(D);
if (VD->getDeclContext()->getSelfClassDecl())
return;
auto nominalDecl = VD->getDeclContext()->getSelfNominalTypeDecl();
if (nominalDecl && isa<StructDecl>(nominalDecl))
return;
diagnoseAndRemoveAttr(attr, diag::invalid_decl_attribute_simple);
}

static Optional<Diag<bool,Type>>
isAcceptableOutletType(Type type, bool &isArray, TypeChecker &TC) {
if (type->isObjCExistentialType() || type->isAny())
Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4048,6 +4048,10 @@ void TypeChecker::validateDecl(ValueDecl *D) {
auto *VD = cast<VarDecl>(D);
auto *PBD = VD->getParentPatternBinding();

// Add the '@_hasStorage' attribute if this property is stored.
if (VD->hasStorage() && !VD->getAttrs().hasAttribute<HasStorageAttr>())
VD->getAttrs().add(new (Context) HasStorageAttr(/*isImplicit=*/true));

// Note that we need to handle the fact that some VarDecls don't
// have a PatternBindingDecl, for example the iterator in a
// 'for ... in ...' loop.
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,7 @@ namespace {
UNINTERESTING_ATTR(Convenience)
UNINTERESTING_ATTR(Semantics)
UNINTERESTING_ATTR(SetterAccess)
UNINTERESTING_ATTR(HasStorage)
UNINTERESTING_ATTR(UIApplicationMain)
UNINTERESTING_ATTR(UsableFromInline)
UNINTERESTING_ATTR(ObjCNonLazyRealization)
Expand All @@ -1224,7 +1225,6 @@ namespace {
UNINTERESTING_ATTR(SynthesizedProtocol)
UNINTERESTING_ATTR(RequiresStoredPropertyInits)
UNINTERESTING_ATTR(Transparent)
UNINTERESTING_ATTR(HasStorage)
UNINTERESTING_ATTR(Testable)

UNINTERESTING_ATTR(WarnUnqualifiedAccess)
Expand Down
26 changes: 19 additions & 7 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3019,21 +3019,29 @@ ModuleFile::getDeclCheckedImpl(DeclID DID) {

configureStorage(var, opaqueReadOwnership,
readImpl, writeImpl, readWriteImpl, accessors);

if (auto accessLevel = getActualAccessLevel(rawAccessLevel)) {
var->setAccess(*accessLevel);
} else {
auto accessLevel = getActualAccessLevel(rawAccessLevel);
if (!accessLevel) {
error();
return nullptr;
}

var->setAccess(*accessLevel);

if (var->isSettable(nullptr)) {
if (auto setterAccess = getActualAccessLevel(rawSetterAccessLevel)) {
var->setSetterAccess(*setterAccess);
} else {
auto setterAccess = getActualAccessLevel(rawSetterAccessLevel);
if (!setterAccess) {
error();
return nullptr;
}
var->setSetterAccess(*setterAccess);

// If we have a less-accessible setter, honor that by adding the
// setter access attribute.
if (*setterAccess < *accessLevel) {
AddAttribute(
new (ctx) SetterAccessAttr(SourceLoc(), SourceLoc(),
*setterAccess, /*implicit*/true));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unfortunate. I wish I had noticed it at the time, but it's weird to be different from the regular access.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it's unfortunate, but I'm not sure how else to preserve this. The rest of the compiler assumes setter access is determined by the presence of this attribute. If there's a better way than synthesizing this here, I'm happy to change it.

}
}

if (isImplicit)
Expand All @@ -3044,6 +3052,10 @@ ModuleFile::getDeclCheckedImpl(DeclID DID) {
if (var->getOverriddenDecl())
AddAttribute(new (ctx) OverrideAttr(SourceLoc()));

// Add the @_hasStorage attribute if this var has storage.
if (var->hasStorage())
AddAttribute(new (ctx) HasStorageAttr(/*isImplicit:*/true));

break;
}

Expand Down
4 changes: 3 additions & 1 deletion test/ParseableInterface/access-filter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ extension UFIProto {

// CHECK: extension PublicStruct {{[{]$}}
extension PublicStruct {
// CHECK: public private(set) static var secretlySettable: Int{{$}}
// CHECK: @_hasInitialValue public static var secretlySettable: Int {
// CHECK-NEXT: get
// CHECK-NEXT: }
public private(set) static var secretlySettable: Int = 0
} // CHECK: {{^[}]$}}

Expand Down
Loading