Skip to content

[5.0] Cherry-pick parseable interface generation improvements #21259

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
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
16 changes: 10 additions & 6 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ def module_cache_path : Separate<["-"], "module-cache-path">,
Flags<[FrontendOption, DoesNotAffectIncrementalBuild, ArgumentIsPath]>,
HelpText<"Specifies the Clang module cache path">;

def module_name : Separate<["-"], "module-name">, Flags<[FrontendOption, ParseableInterfaceOption]>,
def module_name : Separate<["-"], "module-name">,
Flags<[FrontendOption, ParseableInterfaceOption]>,
HelpText<"Name of the module to build">;
def module_name_EQ : Joined<["-"], "module-name=">, Flags<[FrontendOption]>,
Alias<module_name>;
Expand Down Expand Up @@ -464,17 +465,20 @@ def Xlinker : Separate<["-"], "Xlinker">,

def O_Group : OptionGroup<"<optimization level options>">;

def Onone : Flag<["-"], "Onone">, Group<O_Group>, Flags<[FrontendOption]>,
def Onone : Flag<["-"], "Onone">, Group<O_Group>,
Flags<[FrontendOption, ParseableInterfaceOption]>,
HelpText<"Compile without any optimization">;
def O : Flag<["-"], "O">, Group<O_Group>, Flags<[FrontendOption]>,
def O : Flag<["-"], "O">, Group<O_Group>,
Flags<[FrontendOption, ParseableInterfaceOption]>,
HelpText<"Compile with optimizations">;
def Osize : Flag<["-"], "Osize">, Group<O_Group>, Flags<[FrontendOption]>,
def Osize : Flag<["-"], "Osize">, Group<O_Group>,
Flags<[FrontendOption, ParseableInterfaceOption]>,
HelpText<"Compile with optimizations and target small code size">;
def Ounchecked : Flag<["-"], "Ounchecked">, Group<O_Group>,
Flags<[FrontendOption]>,
Flags<[FrontendOption, ParseableInterfaceOption]>,
HelpText<"Compile with optimizations and remove runtime safety checks">;
def Oplayground : Flag<["-"], "Oplayground">, Group<O_Group>,
Flags<[HelpHidden, FrontendOption]>,
Flags<[HelpHidden, FrontendOption, ParseableInterfaceOption]>,
HelpText<"Compile with optimizations appropriate for a playground">;

def RemoveRuntimeAsserts : Flag<["-"], "remove-runtime-asserts">,
Expand Down
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 @@ -1610,19 +1625,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 @@ -1759,12 +1783,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 @@ -2088,6 +2108,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 @@ -2319,7 +2345,6 @@ void PrintAST::visitProtocolDecl(ProtocolDecl *decl) {
Options.BracketOptions.shouldCloseNominal(decl));
}
}

static bool isStructOrClassContext(DeclContext *dc) {
auto *nominal = dc->getSelfNominalTypeDecl();
if (nominal == nullptr)
Expand Down
28 changes: 26 additions & 2 deletions lib/Frontend/ParseableInterfaceSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/FileSystem.h"
#include "swift/AST/Module.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/Frontend/Frontend.h"
#include "swift/Frontend/ParseableInterfaceSupport.h"
#include "swift/Frontend/PrintingDiagnosticConsumer.h"
Expand Down Expand Up @@ -629,6 +630,9 @@ class InheritedProtocolCollector {
return;
}

if (!isPublicOrUsableFromInline(nominal))
return;

map[nominal].recordProtocols(directlyInherited);

// Recurse to find any nested types.
Expand All @@ -647,16 +651,33 @@ class InheritedProtocolCollector {
return;

const NominalTypeDecl *nominal = extension->getExtendedNominal();
if (!isPublicOrUsableFromInline(nominal))
return;

map[nominal].recordConditionalConformances(extension->getInherited());
// No recursion here because extensions are never nested.
}

/// Returns true if the conformance of \p nominal to \p proto is declared in
/// module \p M.
static bool conformanceDeclaredInModule(ModuleDecl *M,
const NominalTypeDecl *nominal,
ProtocolDecl *proto) {
SmallVector<ProtocolConformance *, 4> conformances;
nominal->lookupConformance(M, proto, conformances);
return llvm::all_of(conformances,
[M](const ProtocolConformance *conformance) -> bool {
return M == conformance->getDeclContext()->getParentModule();
});
}

/// If there were any public protocols that need to be printed (i.e. they
/// weren't conformed to explicitly or inherited by another printed protocol),
/// do so now by printing a dummy extension on \p nominal to \p out.
void
printSynthesizedExtensionIfNeeded(raw_ostream &out,
const PrintOptions &printOptions,
ModuleDecl *M,
const NominalTypeDecl *nominal) const {
if (ExtraProtocols.empty())
return;
Expand All @@ -682,10 +703,13 @@ class InheritedProtocolCollector {
[&](ProtocolDecl *inherited) -> TypeWalker::Action {
if (!handledProtocols.insert(inherited).second)
return TypeWalker::Action::SkipChildren;
if (isPublicOrUsableFromInline(inherited)) {

if (isPublicOrUsableFromInline(inherited) &&
conformanceDeclaredInModule(M, nominal, inherited)) {
protocolsToPrint.push_back(inherited);
return TypeWalker::Action::SkipChildren;
}

return TypeWalker::Action::Continue;
});
}
Expand Down Expand Up @@ -769,7 +793,7 @@ bool swift::emitParseableInterface(raw_ostream &out,
for (const auto &nominalAndCollector : inheritedProtocolMap) {
const NominalTypeDecl *nominal = nominalAndCollector.first;
const InheritedProtocolCollector &collector = nominalAndCollector.second;
collector.printSynthesizedExtensionIfNeeded(out, printOptions, nominal);
collector.printSynthesizedExtensionIfNeeded(out, printOptions, M, nominal);
needDummyProtocolDeclaration |=
collector.printInaccessibleConformanceExtensionIfNeeded(out,
printOptions,
Expand Down
86 changes: 84 additions & 2 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4804,6 +4804,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(
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 @@ -4962,10 +5028,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
Loading