Skip to content

Fix #10217: Avoid exponential behavior in derivesFrom #10271

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

Merged
merged 7 commits into from
Nov 11, 2020

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Nov 10, 2020

No description provided.

@odersky
Copy link
Contributor Author

odersky commented Nov 10, 2020

isBottomType calls derivesFom again, which caused exponential behavior.

Comment on lines 245 to 247
loop(tp.tp1) && loop(tp.tp2)
|| tp.tp1.isNullType && loop(tp.tp2)
|| tp.tp2.isNullType && loop(tp.tp2)
Copy link
Member

Choose a reason for hiding this comment

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

Ther's a typo in here (loop(tp.tp2) should be loop(tp.tp1)), and this isn't quite the same thing as before; it doesn't handle T | Nothing, just T | Null. Instead of changing this I suggest changing isBottomType / isBottomTypeAfterErasure to use tp.isRef instead of tp.derivesFrom (you can't have a subclass of Null or Nothing anyway).

Copy link
Contributor Author

@odersky odersky Nov 10, 2020

Choose a reason for hiding this comment

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

Changing derivesFrom to isRef does not work for unions and intersections though.

if defn.isBottomType(tp.tp1) then loop(tp.tp2)
else loop(tp.tp1) && (defn.isBottomType(tp.tp2) || loop(tp.tp2))
loop(tp.tp1) && loop(tp.tp2)
|| tp.tp1.isNullOrNothingType && loop(tp.tp2)
Copy link
Member

Choose a reason for hiding this comment

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

isBottomType doesn't return true for null when ctx.explicitNulls && !ctx.phase.erasedTypes is true but this will, breaking null-safety.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. I changed it so that isBottomType no longer calls derivesFrom.

if defn.isBottomType(tp.tp1) then loop(tp.tp2)
else loop(tp.tp1) && (defn.isBottomType(tp.tp2) || loop(tp.tp2))
loop(tp.tp1) && (loop(tp.tp2) || defn.isBottomType(tp.tp2))
|| defn.isBottomType(tp.tp1) && loop(tp.tp2)
Copy link
Member

Choose a reason for hiding this comment

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

I think the behaviour of this is same as before, but if tp = Null | (Null | (Null | ( ... | A), wouldn't tp.derivesFrom(NullClass) cause the expensive loop(tp.tp2) to run two times? So I think use if is better here.

Copy link
Member

Choose a reason for hiding this comment

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

Also, do we need to make sure OrType(NullType, NothingType).derivesFrom(NothingClass) == false?


class Foo[T]

val f = Foo[A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U]
Copy link
Member

Choose a reason for hiding this comment

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

Can we add another test Null | (Null | (Null | ( ... | A) here?

Also test right-associative combinations and combinations with Null
Need to widen before testing
Now uses a new method hasClassSymbol instead of recursively
calling derivesFrom.
isNothing -> isExactlyNothing
isTopType -> isExactlyAny

Note: there is alrady an isAny, which is isRef(AnyClass) and therefore
more general.

Also: Move isBottomType from defn to Type.
@odersky
Copy link
Contributor Author

odersky commented Nov 11, 2020

I changed isBottomType to use a new method, which is linked with the classSymbols abstraction. I think that should be the right way to characterize it.

I also did some moving around and renaming to standardize the way we test for top and bottom types.

@odersky odersky merged commit c1e41ed into scala:master Nov 11, 2020
@odersky odersky deleted the fix-#10217 branch November 11, 2020 14:08
@Kordyjan Kordyjan added this to the 3.0.0 milestone Aug 2, 2023
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