Skip to content

Fix #10769: change synthesized type in def ordinal #10785

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 3 commits into from
Jan 7, 2021

Conversation

liufengyun
Copy link
Contributor

Fix #10769: change synthesized type in def ordinal

As pointed out by @smarter in #10769, for the following code:

package stm

trait STMLike[F[_]] {
  import Internals._

  sealed abstract class Txn[+A] {}

  object Txn {
def abort[A](e: Throwable): Txn[A] = Abort(e)
  }

  object Internals {
case class Abort(error: Throwable) extends Txn[Nothing]
  }

}

The compiler synthesized the following code for the object Txn:

object Txn {
  type MirroredMonoType = STMLike.this.Txn[?]
  def ordinal(x: Txn.MirroredMonoType): Int =
x match {
      case _:stm.STMLike.Internals.Abort => 0
}

  def abort[A](e: Throwable): Txn[A] = Abort(e)
}

In the method ordinal, the type for Internals.Abort is

TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class stm)),trait STMLike)),module class Internals$)),class Abort)

This type is incorrect, as we are not in the object Internals. The
explicit outer can only handle such types if it's static. In this
case, the object is not static, thus it crashes the explicit outer.

Co-authored-by: Guillaume Martres [email protected]

Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

  • We need reachableTermRef, reachableTypeRef and reachableRawTypeRef, all of those can be useful (and for ordinal we need reachableTermRef too since we can have a child object in an inner object).
  • Instead of using a TypeMap, I would suggest trying to define them directly in a recursive manner like termRef and typeRef
  • Instead of special-casing objects, I would suggest also replacing references to inner classes A.this.B with A#B, this could be used to fix exception caught when loading class C: Cyclic reference involving class C #10888 (comment) nicely and get rid of processInner in ClassfileParser
  • I think this is useful enough to be defined directly in SymDenotations, but that's not super important
  • This is likely useful enough to be exposed in tasty reflect, but that can be done later.

@smarter smarter added this to the 3.0.0-RC1 milestone Dec 22, 2020
@liufengyun
Copy link
Contributor Author

liufengyun commented Jan 6, 2021

  • Instead of special-casing objects, I would suggest also replacing references to inner classes A.this.B with A#B, this could be used to fix #10888 (comment) nicely and get rid of processInner in ClassfileParser

I'm not sure about this change, as it changes the meaning of the types. It might work for Java, but generally does not work for Dotty.

Updated: #11006 is proposed to fix the issue above separately.

@liufengyun liufengyun force-pushed the fix-10769 branch 2 times, most recently from 0355247 to 98267e6 Compare January 6, 2021 11:08
@liufengyun liufengyun requested a review from smarter January 6, 2021 14:17
Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

Instead of special-casing objects, I would suggest also replacing references to inner classes A.this.B with A#B, this could be used to fix #10888 (comment) nicely and get rid of processInner in ClassfileParser

I'm not sure about this change, as it changes the meaning of the types. It might work for Java, but generally does not work for Dotty.

The type A.this.B is not meaningful if we're not inside A or a subclass of A. Imagine that we change the test case to use an inner class instead of an inner object:

package stm

trait STMLike[F[_]] {
  import Internals._

  sealed abstract class Txn[+A] {}

  object Txn {
    def abort[A](e: Throwable): Txn[A] = Abort(e)
  }

  class Internals {
    case class Abort(error: Throwable) extends Txn[Nothing]
  }

}

In the generated ordinal method for Txn, we want to have a case _: Internals#Abort, because case _: Internals.this.Abort isn't valid.

@liufengyun
Copy link
Contributor Author

The type A.this.B is not meaningful if we're not inside A or a subclass of A.

I agree. However, given that we don't know the context where the method is called, it's dangerous. We could make the change A.this.B -> A#B more than necessary, which may potentially change the semantics of typed patterns that requires outer testing.

On the other hand, in the current usage of .reachableTermRef and .reachableTypeRef, there is already an explicit precondition in whyNotGenericSum:

   /**
    *   - all of its children are addressable through a path from its companion object
    */
    def whyNotGenericSum(using Context): String = ...

With such a precondition, the usage of reachableTermRef and reachableTypeRef will be safe (See added test).

Generally, I think similar care should be taken in doing complex things with symbol, as it is really error-prone. The suggested changes don't help avoid such errors, and it may potentially cause other problems, thus I'd suggest stay with the current solution.

(Interestingly, all such use cases I know of are related to children of sealed classes.)

@smarter
Copy link
Member

smarter commented Jan 6, 2021

there is already an explicit precondition in whyNotGenericSum

Ah thanks, I had missed that!

@smarter
Copy link
Member

smarter commented Jan 6, 2021

We could make the change A.this.B -> A#B more than necessary, which may potentially change the semantics of typed patterns that requires outer testing.

Right, to be correct we would have to check the current ctx.owner to decide whether we should do this change, but if we don't have usecases for that (though it could be useful for tasty-reflection?), we don't have to do it now.

As pointed out by @smarter in scala#10769, for the following code:

    package stm

    trait STMLike[F[_]] {
      import Internals._

      sealed abstract class Txn[+A] {}

      object Txn {
	def abort[A](e: Throwable): Txn[A] = Abort(e)
      }

      object Internals {
	case class Abort(error: Throwable) extends Txn[Nothing]
      }

    }

The compiler synthesized the following code for the object `Txn`:

    object Txn {
      type MirroredMonoType = STMLike.this.Txn[?]
      def ordinal(x: Txn.MirroredMonoType): Int =
	x match {
          case _:stm.STMLike.Internals.Abort => 0
	}

      def abort[A](e: Throwable): Txn[A] = Abort(e)
    }

In the method `ordinal`, the type for `Internals.Abort` is

    TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class stm)),trait STMLike)),module class Internals$)),class Abort)

This type is incorrect, as we are not in the object `Internals`. The
explicit outer can only handle such types if it's static. In this
case, the object is not static, thus it crashes the explicit outer.

Co-authored-by: Guillaume Martres <[email protected]>
The first test ensures that the mirror is generated.
The second test will not have mirrors generated.
@liufengyun liufengyun requested a review from smarter January 7, 2021 16:20
else if this.is(Module) then
TermRef(owner.reachableThisType, this.sourceModule)
else
ThisType.raw(TypeRef(owner.reachableThisType, symbol.asType))
Copy link
Member

Choose a reason for hiding this comment

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

What abotu the ThisType.raw here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one is different, as we need to call owner. reachableThisType.

@@ -1410,6 +1410,33 @@ object SymDenotations {
def namedType(using Context): NamedType =
if (isType) typeRef else termRef

/** The typeRef where `pre.O$.this` is changed to `pre.O.type` if `O` is a non-static object
Copy link
Member

Choose a reason for hiding this comment

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

These documentation comments need to be updated since we also do this for static objects now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

Co-authored-by: Guillaume Martres [email protected]
@liufengyun liufengyun merged commit f6aa8f8 into scala:master Jan 7, 2021
@liufengyun liufengyun deleted the fix-10769 branch January 7, 2021 19:08
@Kordyjan Kordyjan modified the milestones: 3.0.0-RC1, 3.0.0 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.

Crash in ExplicitOuter involving compiler-generated ordinal method in companion of inner sealed class
3 participants