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

Improve opaque types #6567

Merged
merged 19 commits into from
Jun 7, 2019
Merged

Improve opaque types #6567

merged 19 commits into from
Jun 7, 2019

Conversation

odersky
Copy link
Contributor

@odersky odersky commented May 26, 2019

This is an experimental PR to see whether we can make opaque types more useful and (hopefully) simpler to implement.

Planned work:

  • Disallow opaque as a local modifier
  • Make the visibility of opaque type aliases the entire class scope where they are defined (following a suggestion of @smarter)
  • Drop the idea of companion objects of opaque aliases
  • Allow bounds in opaque aliases (following a suggestion of @LPTK)
  • Make equality involving opaque types transitive

The original motivation for this work was to arrive at a better implementation of FlagSet in the dotty compiler. What I would like to have:

  • Three opaque types in object Flags: Flag, FlagSet, and FlagChoice, all implemented as Long.
  • Flag <: FlagSet, Flag <: FlagChoice and no relationship between FlagSet and FlagChoice.

This would make existing patterns of flag usage safer and less confusing, while also increasing their efficiency.

@nafg
Copy link

nafg commented May 26, 2019

Why class scope and not any nearest scope?

@odersky
Copy link
Contributor Author

odersky commented May 27, 2019

Why class scope and not any nearest scope?

They are the same, since opaque types will only be allowed to be defined as members of classes, objects and traits. The restriction is there because it gives us an elegant way to encode opaque types as abstract types.

@Jasper-M
Copy link
Contributor

Drop the idea of companion objects of opaque aliases

Then will the enclosing class form the implicit scope of the opaque type? Or will you have to explicitly import its extension methods every time you use it?

@odersky odersky force-pushed the add-opaque-bounds branch from 6b50086 to 5ffb902 Compare May 27, 2019 08:59
@odersky
Copy link
Contributor Author

odersky commented May 27, 2019

Then will the enclosing class form the implicit scope of the opaque type?

Yes. In fact, that's already the case today. We simply drop the rule that opaque types have companion objects.

@odersky
Copy link
Contributor Author

odersky commented May 29, 2019

An important semantic change of this PR affects comparisons of opaque types.

Previously we had for any type T in

object m {
  type T
}

that m.this.T =:= m.T as long as we are inside object m (outside, m.this.T makes no sense).
Now, assume T is an opaque type.

object m {
  opaque type T = Int
}

Inside m we have m.this.T =:= Int. Is it also true inside m that m.T =:= Int?
Previously, we said no, which means that subtyping and =:= equality were not transitive
in this case. We now say yes. We achieve this by lifting the external reference m to
m.this if we are inside object m. See 73e6bf2.

@odersky odersky marked this pull request as ready for review May 30, 2019 10:50
@odersky odersky force-pushed the add-opaque-bounds branch 2 times, most recently from e52a757 to f4b2c9a Compare May 30, 2019 12:58
@odersky odersky requested a review from abgruszecki May 30, 2019 13:55
@odersky odersky force-pushed the add-opaque-bounds branch from f4b2c9a to 2648718 Compare June 5, 2019 13:32
Copy link
Contributor

@abgruszecki abgruszecki left a comment

Choose a reason for hiding this comment

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

Overall LGTM, comments are minor nitpicks or spotted typos.

compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/core/TypeComparer.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/ast/Desugar.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/core/SymDenotations.scala Outdated Show resolved Hide resolved
@abgruszecki abgruszecki assigned odersky and unassigned abgruszecki Jun 6, 2019
odersky added 13 commits June 6, 2019 23:30
The previous treatment would be incompatible with the new opaque types scheme.
We skip a package object if we look for a term, but consider it
if we look for a type. That way we can handle at the same time overloaded
terms in several source files and opaque types.
With the changes in Typer, it is now possible that a type `T` that is a
member of some package object `pobj` in a package `p` is seen once as
`p.pobj.T` and then also as `p.T`. Type comparer has to make sure that
these two types are seen as equivalent.
This is a template for checking the other flags as well (e.g. the
outstanding problem how to check the `enum` flag could be solved
like this.
The scope where an opaque alias is transparent is now the enclosing
class of the opaque definition. The notion of opaque companion object
has been dropped.
The missing case was discovered while experimenting with different opaque
type encodings. It has nothing to do with opaque types, just with the fact
that we sometimes got a UniqueRefDenotation instead of a SymDenotation. But
this should not make a difference.
Previously we had for any type `T` in
```
object m {
  type T
}
```
that `m.this.T =:= m.T` as long as we are inside object `m` (outside, `m.this.T` makes no sense).
Now, assume `T` is an opaque type.
```
object m {
  opaque type T = Int
}
```
Inside `m` we have `this.m.T =:= Int`. Is it also true inside `m` that `m.T =:= Int`?
Previously, we said no, which means that subtyping and =:= equality were not transitive
in this case. We now say yes. We achieve this by lifting the external reference `m` to
`m.this` if we are inside object `m`.

An example that shows the difference is pos//opaque-groups-params.scala compiled from Tasty.
It turns out we get confused when we call normalizeOpaque as part of normal
completion during computeDenot. Fine tuning normalizeOpaque and calling it
in the window of vulnerability where a type is Opaque but not yet Deferred
fixes the problem. Before the following tests failed when compiled from-tasty:

    tests/run/implicit-specifity.scala failed
    tests/pos/i5720.scala failed
    tests/pos/toplevel-opaque-xm failed
    tests/pos/postconditions.scala failed
odersky added 6 commits June 6, 2019 23:30
Make parameter definitions in DefDef have the same span as the symbols
they define. Previously, this position was not set, which means it would
be set pretty much in an ad-hoc way based on the positions that were set
in the definitions environment. For some reason I cannot quite reconstruct,
the changes to opaque caused pos/exports.scala to fail pickling tests
because an extension parameter symbol symbol a different position after
pickling.
@odersky odersky force-pushed the add-opaque-bounds branch from 2648718 to 44b9562 Compare June 6, 2019 21:40
@odersky odersky merged commit 323c6a7 into scala:master Jun 7, 2019
@allanrenucci allanrenucci deleted the add-opaque-bounds branch June 7, 2019 08:01
@nicolasstucki nicolasstucki mentioned this pull request Jun 7, 2019
@biboudis biboudis added this to the 0.16 Tech Preview milestone Jun 7, 2019
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.

5 participants