Skip to content

Documenting expected behaviour of intersection types #13384

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

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

radeusgd
Copy link
Contributor

@radeusgd radeusgd commented Jun 26, 2025

Pull Request Description

  • As discussed, we try to summarize our common understanding on expected behaviour of intersection types in various circumstances.

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.

- inspecting the type via `case of`, e.g.
```
case x of
a : A -> ...
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens in this case?

Suggested change
a : A -> ...
b : B -> ...
a : A -> ...

```
in the first branch, the `a` has visible type `A` but the full type is still
`a : A & (hidden B)` - the `B` part is hidden, but not removed.
- dispatching a method on one of the types, e.g. calling `x.method_defined_on_A`
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it the same when calling a method defined on a hidden type? x.method_defined_on_B?

will pass the value `x` as `self` to the body of `method_defined_on_A`, the
`self` will have a visible type `A`, but the full type is still
`self : A & (hidden B)` - so the `self` value can be casted if needed.
- normally, calling conversions will 'start over' and the value returned from
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if there are conversions from both A and B to some other type? Do unhidden ones take precedence over hidden ones?

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.

  • Thanks for improving the docs, Radek.
  • I prefer positive tone in the docs
  • e.g. describe how great Enso design is
  • and only then warn what would happen if it wasn't as great

@@ -198,3 +248,10 @@ _has been cast to_. As a special case any value wrapped into an _intersection
type_, but _cast down_ to the original type is `==` and has the same `hash` as
the original value. E.g. `4.2 : Complex&Float : Float` is `==` and has the same
`hash` as `4.2` (in spite it _can be cast to_ `Complex`).

### TODO
Copy link
Member

Choose a reason for hiding this comment

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

What's the purpose of this TODO? If the functionality is broken, can we link to the appropriate issues?

self preserves intersection types, just hides them

That works, doesn't it?

@@ -181,6 +181,56 @@ In short: when a [conversion](../syntax/conversions.md) is needed to satisfy a
type check a new value is created to satisfy just the types requested in the
check.

## Preserving identity of refined types

Intersection types are used to refine a value giving it additional
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
Intersection types are used to refine a value giving it additional
Intersection types are often used to refine a value with additional

this compound type may get _hidden_ when passing around various methods (e.g.
`table:Table` will hide the `DB_Table` part), but the value itself still retains
its identity as a `Table & DB_Table` and can be uncovered via a cast or a case
expression.
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
expression.
expression during runtime.

its identity as a `Table & DB_Table` and can be uncovered via a cast or a case
expression.

It is important to ensure that various operations do not accidentally remove a
Copy link
Member

Choose a reason for hiding this comment

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

  • I don't like the term various operations
  • it only spreads FUD
  • there is no point of being so vague in the documentation
  • if you have to, move it into a "warning" or "notice" section
  • e.g. a colloquial knowledge, not part of the spec

Comment on lines +195 to +207
It is important to ensure that various operations do not accidentally remove a
part of the intersection type as then the value completely loses its part of
functionality. If the `DB_Table` part is not hidden, but completely removed, the
table can no longer be casted to `DB_Table` and used as such. This is confusing
for users, as a database table cannot suddenly stop being a database table just
by passing it around (including method calls) or casting.

Consider a multi value `x : A & B`. We indicate hidden types as `(hidden T)` -
they are not visible in the actual type, but can be uncovered via casts or
reflection.

To ensure the above properties, the interpreter ensures that the following
operations only hide the intersection type, but do not remove it:
Copy link
Member

Choose a reason for hiding this comment

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

Here is a short version:

Suggested change
It is important to ensure that various operations do not accidentally remove a
part of the intersection type as then the value completely loses its part of
functionality. If the `DB_Table` part is not hidden, but completely removed, the
table can no longer be casted to `DB_Table` and used as such. This is confusing
for users, as a database table cannot suddenly stop being a database table just
by passing it around (including method calls) or casting.
Consider a multi value `x : A & B`. We indicate hidden types as `(hidden T)` -
they are not visible in the actual type, but can be uncovered via casts or
reflection.
To ensure the above properties, the interpreter ensures that the following
operations only hide the intersection type, but do not remove it:
To ensure intersection types properly propagate thru the Enso program the
basic language constructs are designed to handle them properly. Namely:

the actual wording can be different, but I'd prefer the positive focus rather than description of catharsis in the docs.

- similarly, calling `B.from x` will return a value of type `B & (hidden A)` -
the `A` part is still there (only hidden), because no actual conversion code
had to be run.

Copy link
Member

Choose a reason for hiding this comment

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

Please note the use of these operations rather than various operations ;-)

Suggested change
It is important to ensure these operations do not remove a part of the intersection type.
Otherwise the value loses part of its functionality which would have a detrimental effect
in the GUI, confusing users.
If the `DB_Table` part is not kept as hidden, but completely removed, the
table can no longer be casted to `DB_Table` and used as such.

A little Cassandra's warning at the end may be acceptable...

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.

4 participants