Skip to content

[InterfaceGen] Print property initializers in resilient, fixed-layout types #19619

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 1 commit into from
Oct 6, 2018
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
114 changes: 86 additions & 28 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1864,76 +1864,114 @@ class ExtensionRange {
/// Pattern and Initialization expression. The pattern is always present, but
/// the initializer can be null if there is none.
class PatternBindingEntry {
Pattern *ThePattern;

/// The location of the equal '=' token.
SourceLoc EqualLoc;

enum class Flags {
Checked = 1 << 0,
Removed = 1 << 1,
Lazy = 1 << 2,
Lazy = 1 << 2
};
llvm::PointerIntPair<Pattern *, 3, OptionSet<Flags>> PatternAndFlags;

struct ExprAndEqualLoc {
// When the initializer is removed we don't actually clear the pointer
// because we might need to get initializer's source range. Since the
// initializer is ASTContext-allocated it is safe.
Expr *Node;
/// The location of the equal '=' token.
SourceLoc EqualLoc;
};

// When the initializer is removed we don't actually clear the pointer
// because we might need to get initializer's source range. Since the
// initializer is ASTContext-allocated it is safe.
llvm::PointerIntPair<Expr *, 3, OptionSet<Flags>> InitAndFlags;
union {
/// The initializer expression and its '=' token loc.
ExprAndEqualLoc InitExpr;

/// The text of the initializer expression if deserialized from a module.
StringRef InitStringRepresentation;
};

/// The initializer context used for this pattern binding entry.
DeclContext *InitContext = nullptr;
llvm::PointerIntPair<DeclContext *, 1, bool> InitContextAndIsText;

friend class PatternBindingInitializer;

public:
PatternBindingEntry(Pattern *P, SourceLoc EqualLoc, Expr *E,
DeclContext *InitContext)
: ThePattern(P), EqualLoc(EqualLoc), InitAndFlags(E, {}),
InitContext(InitContext) {}
: PatternAndFlags(P, {}), InitExpr({E, EqualLoc}),
InitContextAndIsText({InitContext, false}) {
}

Pattern *getPattern() const { return ThePattern; }
void setPattern(Pattern *P) { ThePattern = P; }
Pattern *getPattern() const { return PatternAndFlags.getPointer(); }
void setPattern(Pattern *P) { PatternAndFlags.setPointer(P); }
Expr *getInit() const {
return (InitAndFlags.getInt().contains(Flags::Removed))
? nullptr : InitAndFlags.getPointer();
if (PatternAndFlags.getInt().contains(Flags::Removed) ||
InitContextAndIsText.getInt())
return nullptr;
return InitExpr.Node;
}
Expr *getNonLazyInit() const {
return isInitializerLazy() ? nullptr : getInit();
}
SourceRange getOrigInitRange() const;
void setInit(Expr *E);

/// Gets the text of the initializer expression, stripping out inactive
/// branches of any #ifs inside the expression.
StringRef getInitStringRepresentation(SmallVectorImpl<char> &scratch) const;

/// Sets the initializer string representation to the string that was
/// deserialized from a partial module.
void setInitStringRepresentation(StringRef str) {
InitStringRepresentation = str;
InitContextAndIsText.setInt(true);
}

/// Whether this pattern entry can generate a string representation of its
/// initializer expression.
bool hasInitStringRepresentation() const;

/// Retrieve the location of the equal '=' token.
SourceLoc getEqualLoc() const { return EqualLoc; }
SourceLoc getEqualLoc() const {
return InitContextAndIsText.getInt() ? SourceLoc() : InitExpr.EqualLoc;
}

/// Set the location of the equal '=' token.
void setEqualLoc(SourceLoc equalLoc) { EqualLoc = equalLoc; }
void setEqualLoc(SourceLoc equalLoc) {
assert(!InitContextAndIsText.getInt() &&
"cannot set equal loc for textual initializer");
InitExpr.EqualLoc = equalLoc;
}

/// Retrieve the initializer as it was written in the source.
Expr *getInitAsWritten() const { return InitAndFlags.getPointer(); }
Expr *getInitAsWritten() const {
return InitContextAndIsText.getInt() ? nullptr : InitExpr.Node;
}

bool isInitializerChecked() const {
return InitAndFlags.getInt().contains(Flags::Checked);
return PatternAndFlags.getInt().contains(Flags::Checked);
}
void setInitializerChecked() {
InitAndFlags.setInt(InitAndFlags.getInt() | Flags::Checked);
PatternAndFlags.setInt(PatternAndFlags.getInt() | Flags::Checked);
}

bool isInitializerLazy() const {
return InitAndFlags.getInt().contains(Flags::Lazy);
return PatternAndFlags.getInt().contains(Flags::Lazy);
}
void setInitializerLazy() {
InitAndFlags.setInt(InitAndFlags.getInt() | Flags::Lazy);
PatternAndFlags.setInt(PatternAndFlags.getInt() | Flags::Lazy);
}

// Return the first variable initialized by this pattern.
VarDecl *getAnchoringVarDecl() const;

// Retrieve the declaration context for the initializer.
DeclContext *getInitContext() const { return InitContext; }
DeclContext *getInitContext() const {
return InitContextAndIsText.getPointer();
}

/// Override the initializer context.
void setInitContext(DeclContext *dc) { InitContext = dc; }
void setInitContext(DeclContext *dc) {
InitContextAndIsText.setPointer(dc);
}

/// Retrieve the source range covered by this pattern binding.
///
Expand Down Expand Up @@ -2007,6 +2045,10 @@ class PatternBindingDecl final : public Decl,
return const_cast<PatternBindingDecl*>(this)->getMutablePatternList();
}

void setInitStringRepresentation(unsigned i, StringRef str) {
getMutablePatternList()[i].setInitStringRepresentation(str);
}

Expr *getInit(unsigned i) const {
return getPatternList()[i].getInit();
}
Expand Down Expand Up @@ -2039,7 +2081,8 @@ class PatternBindingDecl final : public Decl,

/// Return the PatternEntry (a pattern + initializer pair) for the specified
/// VarDecl.
PatternBindingEntry getPatternEntryForVarDecl(const VarDecl *VD) const {
const PatternBindingEntry &getPatternEntryForVarDecl(
const VarDecl *VD) const {
return getPatternList()[getPatternEntryIndexForVarDecl(VD)];
}

Expand Down Expand Up @@ -2097,7 +2140,7 @@ class PatternBindingDecl final : public Decl,
static bool classof(const Decl *D) {
return D->getKind() == DeclKind::PatternBinding;
}

private:
MutableArrayRef<PatternBindingEntry> getMutablePatternList() {
// Pattern entries are tail allocated.
Expand Down Expand Up @@ -4606,6 +4649,14 @@ class VarDecl : public AbstractStorageDecl {
return nullptr;
}

// Return whether this VarDecl has an initial value, either by checking
// if it has an initializer in its parent pattern binding or if it has
// the @_hasInitialValue attribute.
bool hasInitialValue() const {
return getAttrs().hasAttribute<HasInitialValueAttr>() ||
getParentInitializer();
}

VarDecl *getOverriddenDecl() const {
return cast_or_null<VarDecl>(AbstractStorageDecl::getOverriddenDecl());
}
Expand Down Expand Up @@ -4687,6 +4738,13 @@ class VarDecl : public AbstractStorageDecl {
void setHasNonPatternBindingInit(bool V = true) {
Bits.VarDecl.HasNonPatternBindingInit = V;
}

/// Determines if this var has an initializer expression that should be
/// exposed to clients.
/// There's a very narrow case when we would: if the decl is an instance
/// member with an initializer expression and the parent type is
/// @_fixed_layout and resides in a resilient module.
bool isInitExposedToClients() const;

/// Is this a special debugger variable?
bool isDebuggerVar() const { return Bits.VarDecl.IsDebuggerVar; }
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Serialization/ModuleFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,9 @@ class ModuleFile

/// Reads inlinable body text from \c DeclTypeCursor, if present.
Optional<StringRef> maybeReadInlinableBodyText();

/// Reads pattern initializer text from \c DeclTypeCursor, if present.
Optional<StringRef> maybeReadPatternInitializerText();
};

template <typename T, typename RawData>
Expand Down
5 changes: 3 additions & 2 deletions include/swift/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const uint16_t 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 VERSION_MINOR = 450; // Last change: don't serialize requirement environment
const uint16_t VERSION_MINOR = 451; // Last change: pattern initializer text

using DeclIDField = BCFixed<31>;

Expand Down Expand Up @@ -1478,7 +1478,8 @@ namespace decls_block {
using PatternBindingInitializerLayout = BCRecordLayout<
PATTERN_BINDING_INITIALIZER_CONTEXT,
DeclIDField, // parent pattern binding decl
BCVBR<3> // binding index in the pattern binding decl
BCVBR<3>, // binding index in the pattern binding decl
BCBlob // initializer text, if present
>;

using DefaultArgumentInitializerLayout = BCRecordLayout<
Expand Down
47 changes: 33 additions & 14 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -842,26 +842,35 @@ void PrintAST::printAttributes(const Decl *D) {
if (Options.SkipAttributes)
return;

// Don't print a redundant 'final' if we are printing a 'static' decl.
// Save the current number of exclude attrs to restore once we're done.
unsigned originalExcludeAttrCount = Options.ExcludeAttrList.size();
if (Options.PrintImplicitAttrs &&
D->getDeclContext()->getSelfClassDecl() &&
getCorrectStaticSpelling(D) == StaticSpellingKind::KeywordStatic) {
Options.ExcludeAttrList.push_back(DAK_Final);
}

// Don't print any contextual decl modifiers.
// We will handle 'mutating' and 'nonmutating' separately.
if (Options.PrintImplicitAttrs && isa<AccessorDecl>(D)) {
if (Options.PrintImplicitAttrs) {

// Don't print a redundant 'final' if we are printing a 'static' decl.
if (D->getDeclContext()->getSelfClassDecl() &&
getCorrectStaticSpelling(D) == StaticSpellingKind::KeywordStatic) {
Options.ExcludeAttrList.push_back(DAK_Final);
}

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

// Don't print any contextual decl modifiers.
// We will handle 'mutating' and 'nonmutating' separately.
if (isa<AccessorDecl>(D)) {
#define EXCLUDE_ATTR(Class) Options.ExcludeAttrList.push_back(DAK_##Class);
#define CONTEXTUAL_DECL_ATTR(X, Class, Y, Z) EXCLUDE_ATTR(Class)
#define CONTEXTUAL_SIMPLE_DECL_ATTR(X, Class, Y, Z) EXCLUDE_ATTR(Class)
#define CONTEXTUAL_DECL_ATTR_ALIAS(X, Class) EXCLUDE_ATTR(Class)
#include "swift/AST/Attr.def"
}
}

// If the declaration is implicitly @objc, print the attribute now.
if (Options.PrintImplicitAttrs) {
// If the declaration is implicitly @objc, print the attribute now.
if (auto VD = dyn_cast<ValueDecl>(D)) {
if (VD->isObjC() && !VD->getAttrs().hasAttribute<ObjCAttr>()) {
Printer.printAttrName("@objc");
Expand Down Expand Up @@ -899,7 +908,10 @@ void PrintAST::printPattern(const Pattern *pattern) {
recordDeclLoc(decl, [&]{
if (Options.OmitNameOfInaccessibleProperties &&
contributesToParentTypeStorage(decl) &&
!isPublicOrUsableFromInline(decl))
!isPublicOrUsableFromInline(decl) &&
// FIXME: We need to figure out a way to generate an entry point
// for the initializer expression without revealing the name.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that's possible, since the mangled name of the initializer contains the property name

Copy link
Contributor

Choose a reason for hiding this comment

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

We don't actually care what the name is, though, since it's not a public entry point. At least, we don't for resilient modules.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we generate a name here it has to be consistent between invocations though, so that you can do a non-WMO build where multiple source files import the same textual interface (and the cached binary form got rebuild between invocations)

Copy link
Contributor

Choose a reason for hiding this comment

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

I would again advocate for the simpler approach, instead of figuring out the exact strategy for when we can elide the name, just be conservative and only elide it if there's no initializer

Copy link
Contributor

Choose a reason for hiding this comment

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

We're talking about a swiftinterface here; it's always one file.

Copy link
Contributor

Choose a reason for hiding this comment

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

And we can't just print the name because the name might conflict with extensions.

!decl->hasInitialValue())
Printer << "_";
else
Printer.printName(named->getBoundName());
Expand Down Expand Up @@ -2045,7 +2057,7 @@ void PrintAST::visitPatternBindingDecl(PatternBindingDecl *decl) {
}

bool isFirst = true;
for (auto entry : decl->getPatternList()) {
for (auto &entry : decl->getPatternList()) {
if (!shouldPrintPattern(entry.getPattern()))
continue;
if (isFirst)
Expand All @@ -2065,6 +2077,13 @@ void PrintAST::visitPatternBindingDecl(PatternBindingDecl *decl) {
if (Options.VarInitializers) {
// FIXME: Implement once we can pretty-print expressions.
}

auto vd = entry.getAnchoringVarDecl();
if (entry.hasInitStringRepresentation() &&
vd->isInitExposedToClients()) {
SmallString<128> scratch;
Printer << " = " << entry.getInitStringRepresentation(scratch);
}
}
}

Expand Down
43 changes: 38 additions & 5 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1181,18 +1181,19 @@ unsigned PatternBindingDecl::getPatternEntryIndexForVarDecl(const VarDecl *VD) c
}

SourceRange PatternBindingEntry::getOrigInitRange() const {
auto Init = InitAndFlags.getPointer();
auto Init = getInitAsWritten();
return Init ? Init->getSourceRange() : SourceRange();
}

void PatternBindingEntry::setInit(Expr *E) {
auto F = InitAndFlags.getInt();
auto F = PatternAndFlags.getInt();
if (E) {
InitAndFlags.setInt(F - Flags::Removed);
InitAndFlags.setPointer(E);
PatternAndFlags.setInt(F - Flags::Removed);
} else {
InitAndFlags.setInt(F | Flags::Removed);
PatternAndFlags.setInt(F | Flags::Removed);
}
InitExpr.Node = E;
InitContextAndIsText.setInt(false);
}

VarDecl *PatternBindingEntry::getAnchoringVarDecl() const {
Expand Down Expand Up @@ -1225,6 +1226,25 @@ SourceRange PatternBindingEntry::getSourceRange(bool omitAccessors) const {
return SourceRange(startLoc, endLoc);
}

bool PatternBindingEntry::hasInitStringRepresentation() const {
if (InitContextAndIsText.getInt())
return !InitStringRepresentation.empty();
return getInit() && getInit()->getSourceRange().isValid();
}

StringRef PatternBindingEntry::getInitStringRepresentation(
SmallVectorImpl<char> &scratch) const {

assert(hasInitStringRepresentation() &&
"must check if pattern has string representation");

if (InitContextAndIsText.getInt() && !InitStringRepresentation.empty())
return InitStringRepresentation;
auto &sourceMgr = getAnchoringVarDecl()->getASTContext().SourceMgr;
auto init = getInit();
return extractInlinableText(sourceMgr, init, scratch);
}

SourceRange PatternBindingDecl::getSourceRange() const {
SourceLoc startLoc = getStartLoc();
SourceLoc endLoc = getPatternList().back().getSourceRange().End;
Expand Down Expand Up @@ -1279,6 +1299,19 @@ VarDecl *PatternBindingDecl::getSingleVar() const {
return nullptr;
}

bool VarDecl::isInitExposedToClients() const {
auto parent = dyn_cast<NominalTypeDecl>(getDeclContext());
if (!parent) return false;
if (!hasInitialValue())
return false;
if (isStatic())
return false;
if (!parent->getAttrs().hasAttribute<FixedLayoutAttr>())
return false;
auto *module = parent->getModuleContext();
return module->getResilienceStrategy() == ResilienceStrategy::Resilient;
}

/// Check whether the given type representation will be
/// default-initializable.
static bool isDefaultInitializable(const TypeRepr *typeRepr) {
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/TypeCheckConstraints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2483,7 +2483,7 @@ bool TypeChecker::typeCheckPatternBinding(PatternBindingDecl *PBD,
// Add the attribute that preserves the "has an initializer" value across
// module generation, as required for TBDGen.
PBD->getPattern(patternNumber)->forEachVariable([&](VarDecl *VD) {
if (VD->hasStorage())
if (VD->hasStorage() && !VD->getAttrs().hasAttribute<HasInitialValueAttr>())
VD->getAttrs().add(new (ctx) HasInitialValueAttr(/*IsImplicit=*/true));
});

Expand Down
Loading