Skip to content

Conversation

Akirathan
Copy link
Member

@Akirathan Akirathan commented Sep 8, 2025

Pull Request Description

Semi-formal spec for method invocation. Documents the desired behavior that is being implemented in #13962.

Important Notes

Checklist

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

  • The documentation has been updated, if necessary.
  • Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    TypeScript,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.
  • Unit tests have been written where possible.
  • If meaningful changes were made to logic or tests affecting Enso Cloud integration in the libraries,
    or the Snowflake database integration, a run of the Extra Tests has been scheduled.
    • If applicable, it is suggested to paste a link to a successful run of the Extra Tests.

@Akirathan Akirathan self-assigned this Sep 8, 2025
@Akirathan Akirathan added the CI: No changelog needed Do not require a changelog entry for this PR. label Sep 8, 2025
@Akirathan
Copy link
Member Author

RFC engine and libs team (@JaroslavTulach , @jdunkerley , @4e6 , @hubertp , @GregoryTravis ): Do you consider this kind of semi-formal specification for method resolution and method evaluation useful? Is it better than looking at our tests or in our code base? Do you want me to finish it? Do you have any other suggestions how to improve it? Note that this PR is WIP, obviously.

- 1.4. If `Receiver` is a polyglot object, go to step 5.
- 1.5. If there is no `Receiver`, go to step 6.

2. **`Receiver` is a type. This call site is a _static method call_:**
Copy link
Member

@JaroslavTulach JaroslavTulach Sep 9, 2025

Choose a reason for hiding this comment

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

To play devil's advocate:

  • Isn't it all a bit different?
  • The 1st step is to find a Type for the receiver
    • for Atom the type is its constructor defining type
    • for module the type is its associated type
    • for a Type it is its eigen type
    • etc.?
  • then lookup the method in the pool of that type or
    • its getSupertype pool and so on
  • if a method is found, invoke it with self being set to the receiver
  • 00577f7 documents/tests the chain of types and shows how we want to use allTypes method
  • if no such a method is found, consider static invocation of instance method!?

and somewhere here my thoughts dry out...

Copy link
Member

Choose a reason for hiding this comment

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

and somewhere here my thoughts dry out...

  • 00577f7 documents/tests the chain of types and shows how we want to use allTypes method
  • I am still relatively satisfied with 00577f7
    • e.g. there are no "static" methods - those are instance methods on Type.getEigentype()
  • the only problem is the "duality of methods from Any"
  • and I believe it is sufficiently solved by saying that:
    • each method found on Any has self argument defaulted to the receiver
    • but it can be changed by self=whatever
  • at the end this sounds like a consistent set of rules

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 have simplified the Method resolution specs in 5f6fdaa according to your suggestions, which makes sense:

  1. Determine type of the Receiver
  2. Symbol lookup on the determined type.

- Every type has an implicit parent type `Any`.
- Except for `Number` which is a special builtin case with a deeper hierarchy.
- `Any` has no parent type.
- 2.2. No parent type is present. Raise `No_Such_Method` panic and stop.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- 2.2. No parent type is present. Raise `No_Such_Method` panic and stop.
- 2.2. No parent type is present. Raise `No_Such_Method` panic and terminate.


### Lexical scope lookup

- If `symbol` is defined in the current lexical scope, select it and stop.
Copy link
Member

@JaroslavTulach JaroslavTulach Sep 13, 2025

Choose a reason for hiding this comment

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

  • This is not really a "dynamic dispatch"
  • but it is an interesting topic
  • and complicated - e.g. term like all transitively imported modules sounds scary
  • I'd move it to its own "lexical dispatch" document

[Method Resolution](#method-resolution) process, that is defined as
`method [self] [parName=[defaultValue]]* = <body expression>`.

If `self` parameter is specified, we call it an _instance method_, otherwise, we
Copy link
Member

@JaroslavTulach JaroslavTulach Sep 13, 2025

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

self argument is always the first argument of each method

It is, but from the engine perspective. From the engine perspective, every method has either a single self argument, or two self arguments. However, from the language perspective, you cannot declare self argument on, for example, module methods, neither can you specify self argument when you call such methods.

Copy link
Member

@JaroslavTulach JaroslavTulach Sep 24, 2025

Choose a reason for hiding this comment

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

two self arguments

  • I don't understand why should there be two self arguments?
    • I heard about type of self and self itself - I am not sure what that would be good for?
  • I don't even know when has a method two such arguments?

neither can you specify self argument when you call such module methods.

That would have little sense. self argument is type checked and there is only a singleton of a module associated type. E.g. specifying any other self to a module method would have to yield Type_Error.

@enso-bot enso-bot bot mentioned this pull request Sep 15, 2025
2 tasks
Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

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

Section 2, 3, 4 seem to be correct.

- `self` argument cannot be specified explicitly
- Go to [Arguments evaluation](#argument-evaluation) to evaluate `argValue`s and
bind them to `argName`s.
### Example (a)
Copy link
Member

Choose a reason for hiding this comment

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

  • Instead of examples in the documentation, put them into a test case and link from here. Maybe?
  • Documentation full of examples has only one future: to get outdated.

Copy link
Member Author

Choose a reason for hiding this comment

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

Valid point. I will definitely add such tests in #13962. This whole documentation is, in fact, the desired behavior that is being implemented in #13962.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added as a checklist point in #13962.

Co-authored-by: Jaroslav Tulach <jaroslav.tulach@enso.org>
@Akirathan Akirathan marked this pull request as ready for review October 2, 2025 09:14
@Akirathan Akirathan requested review from 4e6 and hubertp as code owners October 2, 2025 09:14
Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

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

Another update. Let's differentiate:

  • instance invocation syntax has no self= named argument
  • static syntax for invoking instance method has first argument self=
  • the decision what to search is thus done on syntactic level
  • by presence/absence of the self= argument

@Akirathan
Copy link
Member Author

b15403e updates #13962 to match the new specification

var all = raw.allTypes(ctx.ensoContext());

var exp1 = any.getEigentype();
assertArrayEquals("Any.type only", new Object[] {exp1}, all);
Copy link
Member

@JaroslavTulach JaroslavTulach Oct 3, 2025

Choose a reason for hiding this comment

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

Wrong

  • this is the snapshot of current type hierarchy
  • is it good or bad, @Akirathan?
  • this should mean that when typing Any.to_text to perform an instance method invocation
    • the type of Any is Any.type - which is Any's eigentype
    • if to_text is registered in Any's eigentype (No, it is not! it is registered in Any as instance method!)
    • then it will be found during instance method invocation
  • thus Any.to_text should yield "Any"

Correct

After a discussion with @Akirathan we concluded, that the current behavior is wrong and it should be

Suggested change
assertArrayEquals("Any.type only", new Object[] {exp1}, all);
var exp1 = any.getEigentype();
var exp2 = ctx.ensoContext().getBuiltins().any();
assertArrayEquals("Any.type and Any", new Object[] {exp1, exp2}, all);

that way the instance method dispatch should work for Any.to_text

Copy link
Member Author

Choose a reason for hiding this comment

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

It is bad. On develop, to_text is registered both on Any and on Any.type. But ideally, it should be registered only on Any.

Copy link
Member Author

@Akirathan Akirathan Oct 3, 2025

Choose a reason for hiding this comment

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

I believe that the correct semantics should be:

allTypes(Any.type) == [Any.type, Any]
allTypes(Any) == [Any]
allTypes(Text) == [Text, Any]
allTypes(Text.type) == [Text.type, Any]
allTypes(Integer) == [Integer, Number, Any]

@Akirathan
Copy link
Member Author

I believe that as of ed299f5, this is really the final specification that should work for all our use cases. As demonstrated by the updated tests in 7b58937

@JaroslavTulach
Copy link
Member

I believe that as of ed299f5, this is really the final specification that should work for all our use cases. As demonstrated by the updated tests in 7b58937

This diff is more important than #13961 (comment) and it’s 7b58937 as it shows the diff against current behavior. Can we make it less red?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: No changelog needed Do not require a changelog entry for this PR.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants