Skip to content

[ParseableInterface] Expose non-public types required for layout #20004

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

Closed
wants to merge 4 commits into from

Conversation

harlanhaskins
Copy link
Contributor

@harlanhaskins harlanhaskins commented Oct 23, 2018

This patch does a pass over the module looking for stored properties
that contribute to the parent's storage but that use
non-public-or-usableFromInline types. It marks these types for printing,
while also hiding their non-required members.

For example, in a non-resilient module, a user may write this type:

internal class FileHandle {
  internal var fd: Int32
  init(fileDescriptor: Int32) { self.fd = fileDescriptor }
  deinit { close(fd) }
}

public struct File {
  internal var handle: FileHandle
}

Since handle contributes to the storage of the File type, we need to print it inside the declaration of File. Right now, we print the property as:

  internal var _: FileHandle

which signifies that the value was meant to be internal and not accessible from client code. But this caused an issue, because FileHandle is itself internal, and thus wouldn't be printed. Now, we find decls like FileHandle and elect to print their layout-required members as well. This example would be printed as:

internal class FileHandle {
  internal var _: Int32
}
public struct File {
  internal var _: FileHandle
}

Now, we could take this a step further and hide all the members of FileHandle, as we know it'll always be heap-allocated and pointer-sized, but that's not part of this patch.

It also splits off ShouldPrintForParseableInterface into its own file,
because it's grown significantly since it was first introduced in
ASTPrinter.

This patch does a pass over the module looking for stored properties
that contribute to the parent's storage but that use
non-public-or-usableFromInline types. It marks these types for printing,
while also hiding their non-required members.

It also splits off ShouldPrintForParseableInterface into its own file,
because it's grown significantly since it was first introduced in
ASTPrinter.
@beccadax
Copy link
Contributor

Now that I’m seeing this written down, I wonder: Could you replace FileHandle with AnyObject? I’m not sure how that would work with non-class types—maybe you could declare it as a tuple of its member types, recursively until you hit public or builtin types? And I’m definitely not sure how it would work with existentials...

@harlanhaskins
Copy link
Contributor Author

I wanted to avoid going down the rabbit hole of trying to synthesize layout-compatible types, electing for the simple thing of these partially-hidden stubs.

@jrose-apple
Copy link
Contributor

I actually think we will be able to replace classes that way (though the extra inhabitants are different because AnyObject might be a tagged pointer and a pure Swift reference never will be), but we need basically this for structs and enums, so I think it's okay to move forward this way for now.

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

I have some concerns, but this is a good start!

}

PrintOptions PrintOptions::printParseableInterfaceFile() {
PrintOptions PrintOptions::printParseableInterfaceFile(ModuleDecl *module) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm honestly starting to think this doesn't belong on PrintOptions at all. Only the reusable or generic ones really deserve factory constructors; the rest should live at their use sites. That doesn't have to change in this PR, but it kind of lines up with the spiltting out to a separate file.

return scope.isPublic();
}

bool swift::contributesToParentTypeStorage(const AbstractStorageDecl *ASD) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This one I think should probably just move onto AbstractStorageDecl, though maybe without the "isResilient" part. But that doesn't have to change now.

@harlanhaskins
Copy link
Contributor Author

@jrose-apple I've updated this PR to also support enums and protocols, and added them to the test.

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

This seems nicely along the path we were going down but I have a new concern to work out (see below).

addIfNonPublic(decl);

// Also walk into the RHS of the typealias to find extra types we need.
decl->getUnderlyingTypeLoc().getType().walk(*this);
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't the visiting of the decl be responsible for that?

}

// If we see a nominal type decl, walk its inherited types and generic
// requirements, and continue walking into it children.
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: "it children".


void visitNominalTypeDecl(NominalTypeDecl *decl) {
for (auto inherited : decl->getInherited())
inherited.getType().walk(findTypeDecls);
Copy link
Contributor

Choose a reason for hiding this comment

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

Uh, hm. If we don't print extensions, we might not get all the protocol conformances, which means we might not satisfy a generic constraint where this type is used. Is that a problem with our plan?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, yes that is a problem. Hmm...should I add extensions of this type to the printed set as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

That might work for now. We probably do need to sit down and think about whether printing some kind of "layout token" would be easier all around, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Or rather, walk declared conformances in extensions of this type, marking those for inclusion?

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean, let's go a step further. If these conformances are included, we need the members that satisfy the requirements too, right?

This is starting to sound like it's not an "interface" at all, except for the lack of function bodies.

Copy link
Contributor

@graydon graydon left a comment

Choose a reason for hiding this comment

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

Crude question: have you considered writing this as a request (in the request-evaluator sense)?

@harlanhaskins
Copy link
Contributor Author

@graydon I think the request evaluator would be the wrong model here. Given that this essentially marks a type as required iff it's referenced in a layout-required context, you wouldn't be able to quickly ask the question of a given type. Structuring this as (Type) -> Bool would require walking all var decls every time you make the request, and caching those results. We could structure it as (VarDecl) -> [Type], but then we get to a chicken-and-egg problem where we're only printing this VarDecl after we have already skipped printing the required type.

Structuring this as a single walk where we start with all reachable top-level types and push discovered types onto a worklist seemed to be the right answer here.

@harlanhaskins
Copy link
Contributor Author

harlanhaskins commented Nov 1, 2018

Now, @jrose-apple and I discussed offline about the tradeoffs of this method vs. creating layout-compatible types.

We came up with a set of rules for non-public-or-@usableFromInline types (and please correct me if I'm wrong!):

Type Kind Strategy
Typealiases Recursively expand them in-place
Function types Print their convention, and recursively expand parameters/return value
@objc classes or NSObject-derived classes AnyObject
Pure Swift classes Builtin.NativeObject
Existential types A tuple containing: 1 x Word for value witness table, N x Word for protocol conformance witness tables, 1 x Any/1 x AnyObject depending if the existential is class-bound
Enums We need the layout, so print the original enum type, but expanding its payloads recursively a fake enum type, with its payloads expanded.
Structs Recursively expand all fields into tuples
Metatypes I think we can just use Any.Type?

@jrose-apple
Copy link
Contributor

Enums: We need the layout, so print the original enum type

I think this is still going to have to be some kind of dummy type. "Print a fake enum with its payloads expanded."

@harlanhaskins
Copy link
Contributor Author

Going to close this now, as we're going to take a different direction with this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants