-
Notifications
You must be signed in to change notification settings - Fork 327
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
Adding some practical tests for intersection types in context of using them for 'refining' types #12144
Conversation
group_builder.specify "calling `.throw_on_warning` on an intersection type should not lose the refinements" <| | ||
ab = make_a_and_b | ||
r = ab.throw_on_warning | ||
r.is_a A . should_be_true | ||
r.is_a B . should_be_true | ||
r.a_method . should_equal "A method" | ||
r.b_method . should_equal "B method" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was supposed to be an example for the #12143 bug. But apparently this test is already passing?!
I'm really surprised. This was supposed to be a minimal repro of the example that @JaroslavTulach was working-around in commit 9bb73ae.
@JaroslavTulach any idea why you needed a workaround in the Column code but here it seems to work as expected?
I'm a bit anxious that the behaviour seems to be slightly random - in some contexts the intersection is retained and in some it is lost. That is concerning.
I'd be happy if we can fix that, and ideally before - find a repro that actually fails a similar test and add it to the suite.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After all I've found a repro: b8407da
56e26e6
to
5d4c3e2
Compare
Co-authored-by: AdRiley <riley.ad@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many of these tests seem to be duplicates of existing ones. That in my opinion means:
- more tests is better than less
- it is easier to write a new test than read the existing code
- reviewing the code is hard and spotting a problem close to impossible
- if the tests work without changing the implementation
- then they are likely correct
- and more coverage is always good
Thanks for the tests and also thank you for bug reports found while writing them:
- Calling a method that returns 'itself' on a multi-value should not lose multi-value structure? #12143
- Let
EnsoMultiValue.to_text
delegate to first typeto_text
#11827 multi_value.to
doesn't work on second & further elements of intersection type #11935- Structural pattern matching should still work with intersection types #12142
At least that the list of pending issues I am aware of.
…ec.enso Co-authored-by: AdRiley <riley.ad@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great set of tests for showing how all the different combinations of working with these work.
ab = make_a_and_b | ||
a2 = id_a ab | ||
a2.is_a A . should_be_true | ||
a2.is_a B . should_be_false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- we will have to change this
- because Keep
EnsoMultiValue
asself
when dispatchingAny
instance methods #12170 changes the wayAny
instance methods are dispatched - in your PR
a2.is_a B
changesself
to first value which type hasis_a
- e.g.A
- with Keep
EnsoMultiValue
asself
when dispatchingAny
instance methods #12170 theself
remainsEnsoMultiValue
- and
is_a
checksdispatch
as well asextra
types ofEnsoMultiValue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I see.
The is_a
case is a thing we've been chatting about with @AdRiley today.
After some doubts, I think we reached conclusions that seemingly the current behaviour of is_a
made sense.
If I call x.is_a Integer
it tells me that I can call all Integer
methods on it (without any casting, it already is that value).
Are you telling me that this will no longer be the case? I can check x.is_a Integer
but that does not tell me if I can call x.round
on it? I'm not sure if this is a good step forward.
The dispatch change is of course correct, but perhaps is_a
should rely on a different logic? It is a builtin anyway.
We were considering adding a x.could_be_a
that checks the extra
types. But IMHO the is_a
should focus only on the dispatch
types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could x.is_a Integer return true AND you could call x.round on it, be a good solution?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could x.is_a Integer return true AND you could call x.round on it, be a good solution?
I think I was arguing against that mostly because it makes most static analysis rather infeasible.
The problem is that if I hold a value of type x : T
, all I know statically about it is that it has type T
. If it can have a secondary type (x.is_a U == True
) then I don't really know anything. I can no longer statically check that a method call x.foo
on x
fails because T
does not have method foo
- I cannot rule out that at runtime x
will have a hidden type U
that may have the method foo
and a call would succeed.
So I think that was partially the idea behind the 'hidden' types requiring a cast to 'uncover' them - the cast is visible by static analysis and x:U
assertion tells it "The user asserts that x
also has type U
, if the control flow has continued that means this assertion was correct - so from now on I can infer calls on x
as if it had type U
.
With all this being implicit and not visible in the code, the type inference is rather helpless.
Making type inference work is one of my goals as I think improving it will be beneficial to the UX of Enso library developers (and external contributors in the future). But of course if it is clashing with other goals, we can reconsider the solutions. Still I was hoping that we can find a solution that will allow us to have good intersection type behaviour without sacrificing type inference as a whole.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the ideas was to use x : T & Any
as a kind of cast that asserts x
is of type T
but maybe something more too. Then indeed a value like that could x.is_a U
and allow calling U
methods on it, because x : T & Any
tells us it can be anything.
I like this approach because while after x : T & Any
much of type inference is not that capable, it is a 'local only' problem - elsewhere in the code where I have x : T
I can still infer normally. Not to say that x : T & Any
may have its downsides too (like users perhaps 'accidentally' cutting off some refined types by casting without the & Any
suffix). I know @JaroslavTulach was not a fan of & Any
approaches and had some reasons for that.
Pull Request Description
Important Notes
Checklist
Please ensure that the following checklist has been satisfied before submitting the PR:
Scala,
Java,
TypeScript,
and
Rust
style guides. In case you are using a language not listed above, follow the Rust style guide.
or the Snowflake database integration, a run of the Extra Tests has been scheduled.