diff --git a/compiler/src/dotty/tools/dotc/core/ContextOps.scala b/compiler/src/dotty/tools/dotc/core/ContextOps.scala index 34956d9294c9..594f103b8481 100644 --- a/compiler/src/dotty/tools/dotc/core/ContextOps.scala +++ b/compiler/src/dotty/tools/dotc/core/ContextOps.scala @@ -2,8 +2,8 @@ package dotty.tools.dotc package core import Contexts._, Symbols._, Types._, Flags._, Scopes._, Decorators._, NameOps._ -import Denotations._ -import SymDenotations.LazyType, Names.Name, StdNames.nme +import Denotations._, SymDenotations._ +import Names.Name, StdNames.nme import ast.untpd /** Extension methods for contexts where we want to keep the ctx. syntax */ @@ -34,7 +34,8 @@ object ContextOps: if (elem.name == name) return elem.sym.denot // return self } val pre = ctx.owner.thisType - pre.findMember(name, pre, required, excluded) + if ctx.isJava then javaFindMember(name, pre, required, excluded) + else pre.findMember(name, pre, required, excluded) } else // we are in the outermost context belonging to a class; self is invisible here. See inClassContext. ctx.owner.findMember(name, ctx.owner.thisType, required, excluded) @@ -42,6 +43,42 @@ object ContextOps: ctx.scope.denotsNamed(name).filterWithFlags(required, excluded).toDenot(NoPrefix) } + final def javaFindMember(name: Name, pre: Type, required: FlagSet = EmptyFlags, excluded: FlagSet = EmptyFlags): Denotation = + assert(ctx.isJava) + inContext(ctx) { + + val preSym = pre.typeSymbol + + // 1. Try to search in current type and parents. + val directSearch = pre.findMember(name, pre, required, excluded) + + // 2. Try to search in companion class if current is an object. + def searchCompanionClass = if preSym.is(Flags.Module) then + preSym.companionClass.thisType.findMember(name, pre, required, excluded) + else NoDenotation + + // 3. Try to search in companion objects of super classes. + // In Java code, static inner classes, which we model as members of the companion object, + // can be referenced from an ident in a subclass or by a selection prefixed by the subclass. + def searchSuperCompanionObjects = + val toSearch = if preSym.is(Flags.Module) then + if preSym.companionClass.exists then + preSym.companionClass.asClass.baseClasses + else Nil + else + preSym.asClass.baseClasses + + toSearch.iterator.map { bc => + val pre1 = bc.companionModule.namedType + pre1.findMember(name, pre1, required, excluded) + }.find(_.exists).getOrElse(NoDenotation) + + if preSym.isClass then + directSearch orElse searchCompanionClass orElse searchSuperCompanionObjects + else + directSearch + } + /** A fresh local context with given tree and owner. * Owner might not exist (can happen for self valdefs), in which case * no owner is set in result context diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 44236c168b66..8322c75e5e2d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -751,35 +751,8 @@ object JavaParsers { makeTemplate(List(), statics, List(), false)).withMods((cdef.mods & Flags.RetainedModuleClassFlags).toTermFlags) } - def importCompanionObject(cdef: TypeDef): Tree = - Import( - Ident(cdef.name.toTermName).withSpan(NoSpan), - ImportSelector(Ident(nme.WILDCARD)) :: Nil) - - // Importing the companion object members cannot be done uncritically: see - // ticket #2377 wherein a class contains two static inner classes, each of which - // has a static inner class called "Builder" - this results in an ambiguity error - // when each performs the import in the enclosing class's scope. - // - // To address this I moved the import Companion._ inside the class, as the first - // statement. This should work without compromising the enclosing scope, but may (?) - // end up suffering from the same issues it does in scala - specifically that this - // leaves auxiliary constructors unable to access members of the companion object - // as unqualified identifiers. - def addCompanionObject(statics: List[Tree], cdef: TypeDef): List[Tree] = { - // if there are no statics we can use the original cdef, but we always - // create the companion so import A._ is not an error (see ticket #1700) - val cdefNew = - if (statics.isEmpty) cdef - else { - val template = cdef.rhs.asInstanceOf[Template] - cpy.TypeDef(cdef)(cdef.name, - cpy.Template(template)(body = importCompanionObject(cdef) :: template.body)) - .withMods(cdef.mods) - } - - List(makeCompanionObject(cdefNew, statics), cdefNew) - } + def addCompanionObject(statics: List[Tree], cdef: TypeDef): List[Tree] = + List(makeCompanionObject(cdef, statics), cdef) def importDecl(): List[Tree] = { val start = in.offset @@ -901,16 +874,7 @@ object JavaParsers { members) ++= decls } } - def forwarders(sdef: Tree): List[Tree] = sdef match { - case TypeDef(name, _) if (parentToken == INTERFACE) => - var rhs: Tree = Select(Ident(parentName.toTermName), name) - List(TypeDef(name, rhs).withMods(Modifiers(Flags.Protected))) - case _ => - List() - } - val sdefs = statics.toList - val idefs = members.toList ::: (sdefs flatMap forwarders) - (sdefs, idefs) + (statics.toList, members.toList) } def annotationParents: List[Select] = List( scalaAnnotationDot(tpnme.Annotation), diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index ea702e47e673..dfba38d4efa5 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -4,7 +4,7 @@ package typer import core._ import ast._ -import Contexts._, Constants._, Types._, Symbols._, Names._, Flags._, Decorators._ +import Contexts._, ContextOps._, Constants._, Types._, Symbols._, Names._, Flags._, Decorators._ import ErrorReporting._, Annotations._, Denotations._, SymDenotations._, StdNames._ import util.Spans._ import util.SrcPos @@ -145,7 +145,12 @@ trait TypeAssigner { // this is exactly what Erasure will do. case _ => val pre = maybeSkolemizePrefix(qualType, name) - val mbr = qualType.findMember(name, pre) + val mbr = + if ctx.isJava then + ctx.javaFindMember(name, pre) + else + qualType.findMember(name, pre) + if reallyExists(mbr) then qualType.select(name, mbr) else if qualType.isErroneous || name.toTermName == nme.ERROR then UnspecifiedErrorType else NoType diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 902f8506c870..989f65215377 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -367,7 +367,9 @@ class Typer extends Namer if (qualifies(defDenot)) { val found = if (isSelfDenot(defDenot)) curOwner.enclosingClass.thisType - else { + else if (ctx.isJava && defDenot.symbol.isStatic) { + defDenot.symbol.namedType + } else { val effectiveOwner = if (curOwner.isTerm && defDenot.symbol.maybeOwner.isType) // Don't mix NoPrefix and thisType prefixes, since type comparer diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 0306d29f60d3..e1c454f8972b 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -66,3 +66,4 @@ i2797a # GADT cast applied to singleton type difference i4176-gadt.scala +java-inherited-type1 \ No newline at end of file diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index 41821a5d4d70..873c34a19c42 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -35,3 +35,5 @@ varargs-abstract zero-arity-case-class.scala i12194.scala i12753 +t6138 +t6138-2 \ No newline at end of file diff --git a/tests/pos/java-inherited-type/Client.scala b/tests/pos/java-inherited-type/Client.scala new file mode 100644 index 000000000000..a644363cdd4c --- /dev/null +++ b/tests/pos/java-inherited-type/Client.scala @@ -0,0 +1,19 @@ +object Client { + def test= { + Test.Outer.Nested.sig + Test.Outer.Nested.sig1 + Test.Outer.Nested.sig2 + val o = new Test.Outer + new o.Nested1().sig + new o.Nested1().sig1 + new o.Nested1().sig2 + } + + def test1 = { + val t = new Test + val o = new t.Outer1 + new o.Nested1().sig + new o.Nested1().sig1 + new o.Nested1().sig2 + } +} diff --git a/tests/pos/java-inherited-type/Test.java b/tests/pos/java-inherited-type/Test.java new file mode 100644 index 000000000000..ae89a6559a67 --- /dev/null +++ b/tests/pos/java-inherited-type/Test.java @@ -0,0 +1,30 @@ +public class Test { + static class OuterBase implements OuterBaseInterface { + static class StaticInner {} + class Inner {} + } + interface OuterBaseInterface { + interface InnerFromInterface {} + } + public static class Outer extends OuterBase { + public static class Nested { + public static P sig; // was: "type StaticInner", "not found: type Inner", "not found: type InnerFromInterface" + public static P sig1; // was: "type StaticInner is not a member of Test.Outer" + public static P sig2; + + } + public class Nested1 { + public P sig; // was: "not found: type StaticInner" + public P sig1; // was: "type StaticInner is not a member of Test.Outer" + public P sig2; + } + } + public class Outer1 extends OuterBase { + public class Nested1 { + public P sig; // was: "not found: type StaticInner" + public P sig1; // was: "type StaticInner is not a member of Test.Outer" + public P sig2; + } + } + public static class P{} +} diff --git a/tests/pos/java-inherited-type1/J.java b/tests/pos/java-inherited-type1/J.java new file mode 100644 index 000000000000..ba9963104699 --- /dev/null +++ b/tests/pos/java-inherited-type1/J.java @@ -0,0 +1,9 @@ +class J extends S { + // These references all work in Javac because `object O { class I }` erases to `O$I` + + void select1(S1.Inner1 i) { new S1.Inner1(); } + void ident(Inner i) {} + + void ident1(Inner1 i) {} + void select(S.Inner i) { new S.Inner(); } +} diff --git a/tests/pos/java-inherited-type1/S.scala b/tests/pos/java-inherited-type1/S.scala new file mode 100644 index 000000000000..155efc0e06ba --- /dev/null +++ b/tests/pos/java-inherited-type1/S.scala @@ -0,0 +1,9 @@ +class S extends S1 +object S { + class Inner +} + +class S1 +object S1 { + class Inner1 +} diff --git a/tests/pos/java-inherited-type1/Test.scala b/tests/pos/java-inherited-type1/Test.scala new file mode 100644 index 000000000000..082167342a02 --- /dev/null +++ b/tests/pos/java-inherited-type1/Test.scala @@ -0,0 +1,13 @@ +object Test { + val j = new J + // force completion of these signatures + j.ident(null); + j.ident1(null); + j.select(null); + j.select1(null); + + val message:TestMessage = null + val builder:TestMessage.Builder = message.toBuilder + builder.setName("name") + +} diff --git a/tests/pos/java-inherited-type1/TestMessage.java b/tests/pos/java-inherited-type1/TestMessage.java new file mode 100644 index 000000000000..fac373e302f7 --- /dev/null +++ b/tests/pos/java-inherited-type1/TestMessage.java @@ -0,0 +1,17 @@ +abstract class AbstractMessage { + public static abstract class Builder> { + } +} + +class TestMessage extends AbstractMessage { + + public Builder toBuilder() { + return null; + } + + public static class Builder extends AbstractMessage.Builder { + public Builder setName(String name) { + return this; + } + } +} diff --git a/tests/run/t6138-2.check b/tests/run/t6138-2.check new file mode 100644 index 000000000000..473ecde25dba --- /dev/null +++ b/tests/run/t6138-2.check @@ -0,0 +1 @@ +Foo$Bar was instantiated! diff --git a/tests/run/t6138-2/JavaClass.java b/tests/run/t6138-2/JavaClass.java new file mode 100644 index 000000000000..9774c05a0d91 --- /dev/null +++ b/tests/run/t6138-2/JavaClass.java @@ -0,0 +1,4 @@ +public class JavaClass { + // This is defined in ScalaClass + public static final Foo.Bar bar = new Foo.Bar(); +} \ No newline at end of file diff --git a/tests/run/t6138-2/ScalaClass.scala b/tests/run/t6138-2/ScalaClass.scala new file mode 100644 index 000000000000..0528133cbf2c --- /dev/null +++ b/tests/run/t6138-2/ScalaClass.scala @@ -0,0 +1,18 @@ +/* Similar to t10490 -- but defines `Foo` in the object. + * Placing this test within t10490 makes it work without a fix, that's why it's independent. + * Note that this was already working, we add it to make sure we don't regress + */ + +class Foo +object Foo { + class Bar { + override def toString: String = "Foo$Bar was instantiated!" + } +} + +object Test { + def main(args: Array[String]): Unit = { + // JavaClass is the user of the Scala defined classes + println(JavaClass.bar) + } +} \ No newline at end of file diff --git a/tests/run/t6138/JavaClass.java b/tests/run/t6138/JavaClass.java new file mode 100644 index 000000000000..08b9e0bd55d4 --- /dev/null +++ b/tests/run/t6138/JavaClass.java @@ -0,0 +1,4 @@ +public class JavaClass { + // This is defined in ScalaClass + public static final Foo.Bar bar = (new Foo()).new Bar(); +} \ No newline at end of file diff --git a/tests/run/t6138/ScalaClass.scala b/tests/run/t6138/ScalaClass.scala new file mode 100644 index 000000000000..da3c682b5033 --- /dev/null +++ b/tests/run/t6138/ScalaClass.scala @@ -0,0 +1,13 @@ +class Foo { + class Bar { + override def toString: String = "Foo$Bar was instantiated!" + } +} + +object Test { + def main(args: Array[String]): Unit = { + // JavaClass is the user of the Scala defined classes + println(JavaClass.bar) + //println(JavaClass.baz) + } +} \ No newline at end of file diff --git a/tests/run/t6238.check b/tests/run/t6238.check new file mode 100644 index 000000000000..473ecde25dba --- /dev/null +++ b/tests/run/t6238.check @@ -0,0 +1 @@ +Foo$Bar was instantiated!