Skip to content

Fix overriding Java methods, get rid of JavaMethodType #11361

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 5 commits into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ class Definitions {
}
val resParamRef = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls).typeRef
val methodType = MethodType.companion(
isJava = false,
isContextual = name.isContextFunction,
isImplicit = false,
isErased = name.isErasedFunction)
Expand Down
90 changes: 60 additions & 30 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -582,11 +582,30 @@ object Denotations {
*/
def prefix: Type = NoPrefix

/** Either the Scala or Java signature of the info, depending on where the
* symbol is defined.
*
* Invariants:
* - Before erasure, the signature of a denotation is always equal to the
* signature of its corresponding initial denotation.
* - Two distinct overloads will have SymDenotations with distinct
* signatures (the SELECTin tag in Tasty relies on this to refer to an
* overload unambiguously). Note that this only applies to
* SymDenotations, in general we cannot assume that distinct
* SingleDenotations will have distinct signatures (cf #9050).
*/
final def signature(using Context): Signature =
if (isType) Signature.NotAMethod // don't force info if this is a type SymDenotation
signature(isJava = !isType && symbol.is(JavaDefined))

/** Overload of `signature` which lets the caller pick between the Java and
* Scala signature of the info. Useful to match denotations defined in
* different classes (see `matchesLoosely`).
*/
def signature(isJava: Boolean)(using Context): Signature =
if (isType) Signature.NotAMethod // don't force info if this is a type denotation
else info match {
case info: MethodicType =>
try info.signature
case info: MethodOrPoly =>
try info.signature(isJava)
catch { // !!! DEBUG
case scala.util.control.NonFatal(ex) =>
report.echo(s"cannot take signature of $info")
Expand Down Expand Up @@ -992,34 +1011,45 @@ object Denotations {
symbol.hasTargetName(other.symbol.targetName)
&& matchesLoosely(other)

/** matches without a target name check */
/** `matches` without a target name check.
*
* We consider a Scala method and a Java method to match if they have
* matching Scala signatures. This allows us to override some Java
* definitions even if they have a different erasure (see i8615b,
* i9109b), Erasure takes care of adding any necessary bridge to make
* this work at runtime.
*/
def matchesLoosely(other: SingleDenotation)(using Context): Boolean =
val d = signature.matchDegree(other.signature)
d match
case FullMatch =>
true
case MethodNotAMethodMatch =>
!ctx.erasedTypes && {
val isJava = symbol.is(JavaDefined)
val otherIsJava = other.symbol.is(JavaDefined)
// A Scala zero-parameter method and a Scala non-method always match.
if !isJava && !otherIsJava then
true
// Java allows defining both a field and a zero-parameter method with the same name,
// so they must not match.
else if isJava && otherIsJava then
false
// A Java field never matches a Scala method.
else if isJava then
symbol.is(Method)
else // otherIsJava
other.symbol.is(Method)
}
case ParamMatch =>
// The signatures do not tell us enough to be sure about matching
!ctx.erasedTypes && info.matches(other.info)
case noMatch =>
false
if isType then true
else
val isJava = symbol.is(JavaDefined)
val otherIsJava = other.symbol.is(JavaDefined)
val useJavaSig = isJava && otherIsJava
val sig = signature(isJava = useJavaSig)
val otherSig = other.signature(isJava = useJavaSig)
sig.matchDegree(otherSig) match
case FullMatch =>
true
case MethodNotAMethodMatch =>
!ctx.erasedTypes && {
// A Scala zero-parameter method and a Scala non-method always match.
if !isJava && !otherIsJava then
true
// Java allows defining both a field and a zero-parameter method with the same name,
// so they must not match.
else if isJava && otherIsJava then
false
// A Java field never matches a Scala method.
else if isJava then
symbol.is(Method)
else // otherIsJava
other.symbol.is(Method)
}
case ParamMatch =>
// The signatures do not tell us enough to be sure about matching
!ctx.erasedTypes && info.matches(other.info)
case noMatch =>
false

def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(using Context): SingleDenotation =
if hasUniqueSym && prevDenots.containsSym(symbol) then NoDenotation
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/NamerOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ object NamerOps:
val (isContextual, isImplicit, isErased) =
if params.isEmpty then (false, false, false)
else (params.head.is(Given), params.head.is(Implicit), params.head.is(Erased))
val make = MethodType.companion(isJava = isJava, isContextual = isContextual, isImplicit = isImplicit, isErased = isErased)
val make = MethodType.companion(isContextual = isContextual, isImplicit = isImplicit, isErased = isErased)
if isJava then
for param <- params do
if param.info.isDirectRef(defn.ObjectClass) then param.info = defn.AnyType
Expand Down
5 changes: 2 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,7 @@ object TypeErasure {
!classify(tp).derivesFrom(defn.ObjectClass) &&
!tp.symbol.is(JavaDefined)
case tp: TypeParamRef =>
!classify(tp).derivesFrom(defn.ObjectClass) &&
!tp.binder.resultType.isJavaMethod
!classify(tp).derivesFrom(defn.ObjectClass)
case tp: TypeAlias => isUnboundedGeneric(tp.alias)
case tp: TypeBounds =>
val upper = classify(tp.hi)
Expand Down Expand Up @@ -474,7 +473,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
TypeComparer.orType(this(tp1), this(tp2), isErased = true)
case tp: MethodType =>
def paramErasure(tpToErase: Type) =
erasureFn(tp.isJavaMethod, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
erasureFn(isJava, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
val (names, formals0) = if (tp.isErasedMethod) (Nil, Nil) else (tp.paramNames, tp.paramInfos)
val formals = formals0.mapConserve(paramErasure)
eraseResult(tp.resultType) match {
Expand Down
Loading