Skip to content

Opaque type not transparent when seen from a computed prefix #11277

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

Closed
smarter opened this issue Feb 1, 2021 · 3 comments · Fixed by #11300
Closed

Opaque type not transparent when seen from a computed prefix #11277

smarter opened this issue Feb 1, 2021 · 3 comments · Fixed by #11300
Assignees
Milestone

Comments

@smarter
Copy link
Member

smarter commented Feb 1, 2021

class Foo {
  opaque type Num = Int

  val z = Test.id(this)(1)
}
object Test {
  def id(f: Foo)(x: f.Num): f.Num = x
}
-- [E007] Type Mismatch Error: try/opaque.scala:4:24 ---------------------------
4 |  val z = Test.id(this)(1)
  |                        ^
  |                        Found:    (1 : Int)
  |                        Required: Foo.this.Num

As far as I can tell, the issue is that infoDependsOnPrefix is defined as:
https://github.com/lampepfl/dotty/blob/3b741d67f8631487aa553c52e03ac21157e68563/compiler/src/dotty/tools/dotc/core/Types.scala#L2300-L2301
And membersNeedAsSeenFrom returns true when the prefix is the current thisType, because we don't actually need to do an as-seen-from. But for opaque types we do need to recompute the info as a member of its thisType prefix to reveal the opaque type alias, so my best bet is that we need something like this:

--- compiler/src/dotty/tools/dotc/core/Types.scala
+++ compiler/src/dotty/tools/dotc/core/Types.scala
@@ -2302,6 +2302,7 @@ object Types {
      */
     private def infoDependsOnPrefix(symd: SymDenotation, prefix: Type)(using Context): Boolean =
       symd.maybeOwner.membersNeedAsSeenFrom(prefix) && !symd.is(NonMember)
+      || prefix.isInstanceOf[Types.ThisType] && symd.is(Opaque)

     /** Is this a reference to a class or object member? */
     def isMemberRef(using Context): Boolean = designator match {

Which does fix the original issue, but I don't understand the full opaque type implementation well enough to say if this is good enough (for example there's a seemingly related special case in https://github.com/lampepfl/dotty/blob/3b741d67f8631487aa553c52e03ac21157e68563/compiler/src/dotty/tools/dotc/core/Denotations.scala#L1066-L1076, and I have no idea why). WDYT @odersky ?

@odersky
Copy link
Contributor

odersky commented Feb 2, 2021

It is inherent in opaque types that they are opaque when used from a prefix. After all, the alias is recorded in the self type of the enclosing class, and that self type is only visible if the prefix is this. So you can think of opaque types as private[this] transparent.

@odersky
Copy link
Contributor

odersky commented Feb 2, 2021

It would be good to update the docs to reflect this.

@odersky odersky assigned b-studios and unassigned odersky Feb 2, 2021
@smarter
Copy link
Member Author

smarter commented Feb 2, 2021

It is inherent in opaque types that they are opaque when used from a prefix.

This is not true when the prefix in question is the ThisType of the type defining the opaque type:

class Foo {
  opaque type Num = Int

  val z: Foo.this.Num = 1
}

This works perfectly well, which is not surprising: Num and Foo.this.Num are exactly the same type, it's just that one is written with a qualifier elided and the other isn't. But the error message I got above was:

-- [E007] Type Mismatch Error: try/opaque.scala:4:24 ---------------------------
4 |  val z = Test.id(this)(1)
  |                        ^
  |                        Found:    (1 : Int)
  |                        Required: Foo.this.Num

This again mentions Foo.this.Num, bu this time the compiler doesn't think that Int is a subtype of that type, but it's exactly the same type that works when you write it manually! The only difference is that the compiler created these types in different ways and ended up assigning them different denotations, this is purely an implementation detail and I don't see how this could be specified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants