diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 9491bdab9de8..33a1b6ae789e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -5,7 +5,6 @@ package core import Symbols.*, Types.*, Contexts.*, Flags.*, Names.*, StdNames.*, Phases.* import Flags.JavaDefined import Uniques.unique -import TypeOps.makePackageObjPrefixExplicit import backend.sjs.JSDefinitions import transform.ExplicitOuter.* import transform.ValueClasses.* diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 79eac7bde38d..a6981c7c6e95 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -560,36 +560,6 @@ object TypeOps: widenMap(tp) } - /** If `tpe` is of the form `p.x` where `p` refers to a package - * but `x` is not owned by a package, expand it to - * - * p.package.x - */ - def makePackageObjPrefixExplicit(tpe: NamedType)(using Context): Type = { - def tryInsert(pkgClass: SymDenotation): Type = pkgClass match { - case pkg: PackageClassDenotation => - var sym = tpe.symbol - if !sym.exists && tpe.denot.isOverloaded then - // we know that all alternatives must come from the same package object, since - // otherwise we would get "is already defined" errors. So we can take the first - // symbol we see. - sym = tpe.denot.alternatives.head.symbol - val pobj = pkg.packageObjFor(sym) - if (pobj.exists) tpe.derivedSelect(pobj.termRef) - else tpe - case _ => - tpe - } - if (tpe.symbol.isRoot) - tpe - else - tpe.prefix match { - case pre: ThisType if pre.cls.is(Package) => tryInsert(pre.cls) - case pre: TermRef if pre.symbol.is(Package) => tryInsert(pre.symbol.moduleClass) - case _ => tpe - } - } - /** An argument bounds violation is a triple consisting of * - the argument tree * - a string "upper" or "lower" indicating which bound is violated diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index 485272fe71c5..f343d7227bf8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -3,7 +3,7 @@ package dotc package core import TypeErasure.ErasedValueType -import Types.*, Contexts.*, Symbols.*, Flags.*, Decorators.* +import Types.*, Contexts.*, Symbols.*, Flags.*, Decorators.*, SymDenotations.* import Names.{Name, TermName} import Constants.Constant @@ -186,6 +186,36 @@ class TypeUtils: case self: Types.ThisType => self.cls == cls case _ => false + /** If `self` is of the form `p.x` where `p` refers to a package + * but `x` is not owned by a package, expand it to + * + * p.package.x + */ + def makePackageObjPrefixExplicit(using Context): Type = + def tryInsert(tpe: NamedType, pkgClass: SymDenotation): Type = pkgClass match + case pkg: PackageClassDenotation => + var sym = tpe.symbol + if !sym.exists && tpe.denot.isOverloaded then + // we know that all alternatives must come from the same package object, since + // otherwise we would get "is already defined" errors. So we can take the first + // symbol we see. + sym = tpe.denot.alternatives.head.symbol + val pobj = pkg.packageObjFor(sym) + if pobj.exists then tpe.derivedSelect(pobj.termRef) + else tpe + case _ => + tpe + self match + case tpe: NamedType => + if tpe.symbol.isRoot then + tpe + else + tpe.prefix match + case pre: ThisType if pre.cls.is(Package) => tryInsert(tpe, pre.cls) + case pre: TermRef if pre.symbol.is(Package) => tryInsert(tpe, pre.symbol.moduleClass) + case _ => tpe + case tpe => tpe + /** Strip all outer refinements off this type */ def stripRefinement: Type = self match case self: RefinedOrRecType => self.parent.stripRefinement diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index e62db9af520a..44b305db43c8 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1272,7 +1272,7 @@ class TreeUnpickler(reader: TastyReader, val tpe0 = name match case name: TypeName => TypeRef(qualType, name, denot) case name: TermName => TermRef(qualType, name, denot) - val tpe = TypeOps.makePackageObjPrefixExplicit(tpe0) + val tpe = tpe0.makePackageObjPrefixExplicit ConstFold.Select(untpd.Select(qual, name).withType(tpe)) def completeSelect(name: Name, sig: Signature, target: Name): Select = diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index fd16f0de5f3a..bf5a75ff508a 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -85,7 +85,7 @@ trait TypeAssigner { defn.FromJavaObjectType else tpe match case tpe: NamedType => - val tpe1 = TypeOps.makePackageObjPrefixExplicit(tpe) + val tpe1 = tpe.makePackageObjPrefixExplicit if tpe1 ne tpe then accessibleType(tpe1, superAccess) else diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 817e7baf1c8c..8a633efc1021 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -344,7 +344,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // so we ignore that import. if reallyExists(denot) && !isScalaJsPseudoUnion then if unimported.isEmpty || !unimported.contains(pre.termSymbol) then - return pre.select(name, denot) + return pre.select(name, denot).makePackageObjPrefixExplicit case _ => if imp.importSym.isCompleting then report.warning(i"cyclic ${imp.importSym}, ignored", pos) @@ -504,7 +504,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer defDenot.symbol.owner else curOwner - effectiveOwner.thisType.select(name, defDenot) + effectiveOwner.thisType.select(name, defDenot).makePackageObjPrefixExplicit } if !curOwner.is(Package) || isDefinedInCurrentUnit(defDenot) then result = checkNewOrShadowed(found, Definition) // no need to go further out, we found highest prec entry diff --git a/tests/pos/i18097.1.scala b/tests/pos/i18097.1.scala new file mode 100644 index 000000000000..b7b57467e3b0 --- /dev/null +++ b/tests/pos/i18097.1.scala @@ -0,0 +1,22 @@ +opaque type Pos = Double + +object Pos: + extension (x: Pos) + def mult1(y: Pos): Pos = x * y + +extension (x: Pos) + def mult2(y: Pos): Pos = x * y + +class Test: + def test(key: String, a: Pos, b: Pos): Unit = + val tup1 = new Tuple1(Pos.mult1(a)(b)) + val res1: Pos = tup1._1 + + val tup2 = new Tuple1(a.mult1(b)) + val res2: Pos = tup2._1 + + val tup3 = new Tuple1(mult2(a)(b)) + val res3: Pos = tup3._1 + + val tup4 = new Tuple1(a.mult2(b)) + val res4: Pos = tup4._1 // was error: Found: (tup4._4 : Double) Required: Pos diff --git a/tests/pos/i18097.2.scala b/tests/pos/i18097.2.scala new file mode 100644 index 000000000000..c676479aab42 --- /dev/null +++ b/tests/pos/i18097.2.scala @@ -0,0 +1,13 @@ +opaque type Namespace = List[String] + +object Namespace: + def apply(head: String): Namespace = List(head) + +extension (ns: Namespace) + def appended(segment: String): Namespace = ns.appended(segment) + +object Main: + def main(args: Array[String]): Unit = + val a: Namespace = Namespace("a") + .appended("B") + .appended("c") // was error: Found: List[String] Required: Namespace diff --git a/tests/pos/i18097.2.works.scala b/tests/pos/i18097.2.works.scala new file mode 100644 index 000000000000..3ba8e056a4a5 --- /dev/null +++ b/tests/pos/i18097.2.works.scala @@ -0,0 +1,13 @@ +object Main: + opaque type Namespace = List[String] + + object Namespace: + def apply(head: String): Namespace = List(head) + + extension (ns: Namespace) + def appended(segment: String): Namespace = ns.appended(segment) + + def main(args: Array[String]): Unit = + val a: Namespace = Namespace("a") + .appended("B") + .appended("c") diff --git a/tests/pos/i18097.3/Opaque.scala b/tests/pos/i18097.3/Opaque.scala new file mode 100644 index 000000000000..cb9c9eaedfb3 --- /dev/null +++ b/tests/pos/i18097.3/Opaque.scala @@ -0,0 +1,9 @@ +package test + +type Foo = Unit +val bar: Foo = () + +opaque type Opaque = Unit + +extension (foo: Foo) + def go: Option[Opaque] = ??? diff --git a/tests/pos/i18097.3/Test.scala b/tests/pos/i18097.3/Test.scala new file mode 100644 index 000000000000..38f2199944c2 --- /dev/null +++ b/tests/pos/i18097.3/Test.scala @@ -0,0 +1,13 @@ +package test + +final case class Test(value: Opaque) + +def test: Test = + bar.go match + case Some(value) => Test(value) // was error: Found: (value : Unit) Required: test.Opaque + case _ => ??? + +def test2: Test = + go(bar) match + case Some(value) => Test(value) + case _ => ??? diff --git a/tests/pos/i18097.orig.scala b/tests/pos/i18097.orig.scala new file mode 100644 index 000000000000..092a904f6de4 --- /dev/null +++ b/tests/pos/i18097.orig.scala @@ -0,0 +1,20 @@ +opaque type PositiveNumber = Double + +object PositiveNumber: + extension (x: PositiveNumber) + def mult1(other: PositiveNumber): PositiveNumber = + x * other + +extension (x: PositiveNumber) + def mult2(other: PositiveNumber): PositiveNumber = + x * other + +object Test: + def multMap1[A](x: Map[A, PositiveNumber], num: PositiveNumber): Map[A, PositiveNumber] = x.map((key, value) => key -> value.mult1(num)).toMap + + def multMap2[A](x: Map[A, PositiveNumber], num: PositiveNumber): Map[A, PositiveNumber] = x.map((key, value) => key -> value.mult2(num)).toMap // was error +// ^ +// Cannot prove that (A, Double) <:< (A, V2). +// +// where: V2 is a type variable with constraint <: PositiveNumber + def multMap2_2[A](x: Map[A, PositiveNumber], num: PositiveNumber): Map[A, PositiveNumber] = x.map((key, value) => key -> mult2(value)(num)).toMap