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

Atom constructors can be private #9692

Merged
merged 74 commits into from
Apr 29, 2024
Merged

Conversation

Akirathan
Copy link
Member

@Akirathan Akirathan commented Apr 12, 2024

Closes #8836

Pull Request Description

Atom constructors can be declared as private (project-private). project-private constructors can be called only from the same project. See the encapsulation.md docs for more info.

Important Notes

  • Parser supports an optional body for the private keyword. It now accepts an atom constructors as optional body. Private methods are considered a syntactical error for now.
  • The runtime private access check works on Function and FunctionSchema and AtomConstructor data. No need to fiddle with truffle's frames.
  • If a private constructor is called from different project, a panic with Standard.Base.Errors.Common.Private_Access payload is thrown.
  • Foreign polyglot code cannot access anything private. For foreign code, all the private entities are not visible.
  • Usage of private constructors in pattern matching is checked during compilation.
  • Due to current compiler limitations, users still can reference the project-private constructor without a compilation issue.

Runtime private checks are implemented in such a way, that it should be easy to implement private methods in the future. For private methods, only parser modifications should be necessary.

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • All code follows the
    Scala,
    Java
  • All code has been tested:
    • Unit tests have been written where possible.

@Akirathan Akirathan self-assigned this Apr 12, 2024
@Akirathan Akirathan force-pushed the wip/akirathan/8836-private-ctors branch from 033cd27 to 9750ef8 Compare April 16, 2024 09:29
@Akirathan Akirathan force-pushed the wip/akirathan/8836-private-ctors branch from bb05165 to 85dca02 Compare April 19, 2024 12:10
box syntax::tree::Variant::ConstructorDefinition(_) =>
syntax::Tree::private(keyword, Some(new_body)),
_ => syntax::Tree::private(keyword, Some(body))
.with_error("Unsupported private body."),
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 a user-facing error--it should explain why the syntax is invalid to a user with no knowledge of the parser internals

Copy link
Member Author

Choose a reason for hiding this comment

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

Suggested change
.with_error("Unsupported private body."),
.with_error("Unsupported private entity."),

Is this better? Or do you advice to print the whole body content with something like body.code? Note that body is of type Tree<'{error}> here.

match body_opt {
Some(body) => {
let body_ = body.clone();
let new_body = to_body_statement(body_);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this test would fail: expect_invalid_node("private ConstructorOutsideType"). Whether to_body_statement is applied must depend on whether the expression is in the top level of a type definition, not the expression's content.

The right solution here is a bit complex because when private_keyword is called, we don't know yet if the private invocation occurred in the context of a type-definition body, so we can't tell yet whether the inner expression should be interpreted as a type body statement (and potentially a constructor definition).

What we could do is:

  • If the usage of the keyword is not valid in the default (expression) context, wrap it in an Invalid node (i.e. with_error("The 'private' keyword cannot be applied to any expression outside of a type definition.")).
  • In to_body_statement, recognize (Invalid (Private ...)), discard the outer Invalid, apply the to_body_statement transformation to the body of the private, and then check whether it's valid in context, emitting a new error if it isn't a constructor.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this test would fail: expect_invalid_node("private ConstructorOutsideType"). Whether to_body_statement is applied must depend on whether the expression is in the top level of a type definition, not the expression's content.

@kazcw Yes, it fails. I have added that test in 395544f.

Your proposed solution would work for private constructors. But in the near future, we would like to support private methods as well. Not only methods in types, but also static methods in top scope in a module. Any suggestions how to support the private constructor and be able to parse also private top-scope method in the future?

Copy link
Contributor

Choose a reason for hiding this comment

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

expression_to_statement (where method definitions are recognized) would need a clause analogous to the new logic in to_body_statement.

Copy link
Member Author

Choose a reason for hiding this comment

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

@kazcw Please help. I am struggling to implement your suggestion in fcf2db5. That commit does not compile - there is a borrow checker error that I cannot resolve. Would you please be so kind and fix that commit?

Copy link
Contributor

Choose a reason for hiding this comment

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

I refactored it, pushed @ 4a11104

@Akirathan
Copy link
Member Author

@JaroslavTulach Even with PrivateSymbolsAnalysis compiler pass, that scans all BindingsMap.Resolution metadata in the IR, introduced in 4ba8b53, we can still reference project-private constructor from different project without any compilation error. As you can see in the following IR graph:
local Proj Main gv

There is no BindingsMap.Resolution metadata for the Cons literal - which is in this case known to be a project-private constructor. This is a limitation of our current approach to fully qualified names, and solving it requires huge effort. I am afraid we will have to merge this PR knowing that for these cases, there will be "just" a runtime error...

Comment on lines +8 to +9
# Base_Tests import stuff from Helpers sibling project. So we need this property here.
prefer-local-libraries: true
Copy link
Member

Choose a reason for hiding this comment

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

👍

Comment on lines +64 to +65
# Note that pattern matching on project-private constructor from a different project
# is a compilation error. So we can only test it for the same project.
Copy link
Member

Choose a reason for hiding this comment

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

Nice comment. We could leave a link to a corresponding test in the runtime-integration-tests that tests the compilation error case.

Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines +1411 to +1418
// Mixing public and private constructor is a semantic error, not a syntax error.
// So parsing should be fine.
parseTest(
"""
type My_Type
private Ctor_1 a b
Ctor_2 c d
""");
Copy link
Member

Choose a reason for hiding this comment

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

I appreciate the comment explaining the intention here 👍

Copy link
Member

@radeusgd radeusgd left a comment

Choose a reason for hiding this comment

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

Checked the .enso files and tests checking the overall semantics - all looks good to me

@Akirathan Akirathan merged commit 660c5e7 into develop Apr 29, 2024
36 checks passed
@Akirathan Akirathan deleted the wip/akirathan/8836-private-ctors branch April 29, 2024 12:43
@Akirathan Akirathan mentioned this pull request May 24, 2024
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Clean build required CI runners will be cleaned before and after this PR is built.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement private constructors & deconstructors/getters for types
5 participants