From 8253eb1c51601d7bb6d50ea690b06ecf02bb60cf Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Jul 2022 22:10:32 +0200 Subject: [PATCH 1/3] Fix expansion and unexpansion of mixin qualified names A mixin qualifier foo$A is a qualified name. Previously these were flattened to SimpleNames when expanding with a trait prefix. But then ResolveSuper cannot decompose the name anymore. To fix this, we now treat QualifiedNames specially in the replace calls for SymDenotations#fullNameSeparated and NameOps#unexpanded. Qualifiers are preserved and the replacement proceeds recursively to their underlying part. For some as yet unknown reason we can't do this for TraitSetterNames. It seems these need to be flattened. Fixes #15702 --- .../src/dotty/tools/dotc/core/NameOps.scala | 5 +++- .../src/dotty/tools/dotc/core/Names.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 12 ++++++-- tests/run/i15702.check | 3 ++ tests/run/i15702.scala | 29 +++++++++++++++++++ 5 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 tests/run/i15702.check create mode 100644 tests/run/i15702.scala diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 3c88917d29c9..39df940fe700 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -146,7 +146,10 @@ object NameOps { /** Revert the expanded name. */ def unexpandedName: N = likeSpacedN { - name.replace { case ExpandedName(_, unexp) => unexp } + name.replace { + case ExpandedName(_, unexp) => unexp + case DerivedName(qual, info: QualifiedInfo) => qual.unexpandedName.derived(info) + } } def errorName: N = likeSpacedN(name ++ nme.ERROR) diff --git a/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index 4cf7d17c17db..b1e8da359407 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -69,7 +69,7 @@ object Names { /** Apply rewrite rule given by `f` to some part of this name, skipping and rewrapping * other decorators. - * Stops at derived names whose kind has `definesNewName = true`. + * Stops at `DerivedName`s with infos of kind `QualifiedInfo`. * If `f` does not apply to any part, return name unchanged. */ def replace(f: PartialFunction[Name, Name]): ThisName diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 54ef207759aa..5f5d8c24d03d 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -486,10 +486,18 @@ object SymDenotations { def qualify(n: SimpleName) = val qn = kind(prefix.toTermName, if (filler.isEmpty) n else termName(filler + n)) if kind == FlatName && !encl.is(JavaDefined) then qn.compactified else qn - val fn = name replace { + def expand(name: Name): Name = name.replace { case name: SimpleName => qualify(name) - case name @ AnyQualifiedName(_, _) => qualify(name.toSimpleName) + case name @ DerivedName(qual, info: QualifiedInfo) => + if kind == TraitSetterName then + qualify(name.toSimpleName) + // TODO: Find out why TraitSetterNames can't be kept as QualifiedNames + else + expand(qual).derived(info) + // In all other cases, keep the qualified name, so that it can be recovered later. + // An example where this matters is run/i15702.scala } + val fn = expand(name) if (name.isTypeName) fn.toTypeName else fn.toTermName } diff --git a/tests/run/i15702.check b/tests/run/i15702.check new file mode 100644 index 000000000000..9db2ce96ae1c --- /dev/null +++ b/tests/run/i15702.check @@ -0,0 +1,3 @@ +10 +20 +200 diff --git a/tests/run/i15702.scala b/tests/run/i15702.scala new file mode 100644 index 000000000000..87559219b321 --- /dev/null +++ b/tests/run/i15702.scala @@ -0,0 +1,29 @@ +abstract class A: + val n: Int + def foo(): Int = n + +trait B: + val m: Int + def foo(): Int = m + +trait M extends A with B: + val a: Int + override def foo(): Int = a + super.foo() + +trait N extends A with B: + val b: Int + override def foo(): Int = b * super.foo() + + class Inner: + println(N.super[A].foo()) + println(N.super.foo()) + println(foo()) + +object C extends A with M with N: + val a = 10 + val b = 10 + val m = 10 + val n = 10 + new Inner() + +@main def Test = C \ No newline at end of file From 57fb5d4fa7f97e06dd03bdbde6b30b0932088c37 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 21 Jul 2022 11:23:38 +0200 Subject: [PATCH 2/3] Remove TraitSetter exceptional case from fullNameSeparated Move the logic to traitSetterName. --- .../src/dotty/tools/dotc/core/SymDenotations.scala | 10 +++------- compiler/src/dotty/tools/dotc/transform/Mixin.scala | 4 ++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 5f5d8c24d03d..53a71390124a 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -489,13 +489,9 @@ object SymDenotations { def expand(name: Name): Name = name.replace { case name: SimpleName => qualify(name) case name @ DerivedName(qual, info: QualifiedInfo) => - if kind == TraitSetterName then - qualify(name.toSimpleName) - // TODO: Find out why TraitSetterNames can't be kept as QualifiedNames - else - expand(qual).derived(info) - // In all other cases, keep the qualified name, so that it can be recovered later. - // An example where this matters is run/i15702.scala + expand(qual).derived(info) + // Keep the qualified name, so that it can be recovered later. + // An example where this matters is run/i15702.scala } val fn = expand(name) if (name.isTypeName) fn.toTypeName else fn.toTermName diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index b6aeb7c21511..5ca09dd6188f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -23,7 +23,11 @@ object Mixin { val description: String = "expand trait fields and trait initializers" def traitSetterName(getter: TermSymbol)(using Context): TermName = + extension (name: Name) def qualifiedToSimple = name.replace { + case n @ AnyQualifiedName(_, _) => n.toSimpleName + } getter.ensureNotPrivate.name + .qualifiedToSimple // TODO: Find out why TraitSetterNames can't be defined over QualifiedNames .expandedName(getter.owner, TraitSetterName) .asTermName.syntheticSetterName } From 9f4871bb4bd522fd39ee063425620b9ccee48946 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 21 Jul 2022 11:48:02 +0200 Subject: [PATCH 3/3] Clean up logic using a new method `replaceDeep` --- compiler/src/dotty/tools/dotc/core/NameOps.scala | 3 +-- compiler/src/dotty/tools/dotc/core/Names.scala | 9 ++++++++- .../src/dotty/tools/dotc/core/SymDenotations.scala | 11 +++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 39df940fe700..98fcf0d9e303 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -146,9 +146,8 @@ object NameOps { /** Revert the expanded name. */ def unexpandedName: N = likeSpacedN { - name.replace { + name.replaceDeep { case ExpandedName(_, unexp) => unexp - case DerivedName(qual, info: QualifiedInfo) => qual.unexpandedName.derived(info) } } diff --git a/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index b1e8da359407..f13c3a184bf9 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -69,11 +69,18 @@ object Names { /** Apply rewrite rule given by `f` to some part of this name, skipping and rewrapping * other decorators. - * Stops at `DerivedName`s with infos of kind `QualifiedInfo`. + * Stops at DerivedNames with infos of kind QualifiedInfo. * If `f` does not apply to any part, return name unchanged. */ def replace(f: PartialFunction[Name, Name]): ThisName + /** Same as replace, but does not stop at DerivedNames with infos of kind QualifiedInfo. */ + def replaceDeep(f: PartialFunction[Name, Name]): ThisName = + replace(f.orElse { + case DerivedName(underlying, info: QualifiedInfo) => + underlying.replaceDeep(f).derived(info) + }) + /** If partial function `f` is defined for some part of this name, apply it * in a Some, otherwise None. * Stops at derived names whose kind has `definesNewName = true`. diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 53a71390124a..a1752ccc0976 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -486,15 +486,10 @@ object SymDenotations { def qualify(n: SimpleName) = val qn = kind(prefix.toTermName, if (filler.isEmpty) n else termName(filler + n)) if kind == FlatName && !encl.is(JavaDefined) then qn.compactified else qn - def expand(name: Name): Name = name.replace { - case name: SimpleName => qualify(name) - case name @ DerivedName(qual, info: QualifiedInfo) => - expand(qual).derived(info) - // Keep the qualified name, so that it can be recovered later. - // An example where this matters is run/i15702.scala + val fn = name.replaceDeep { + case n: SimpleName => qualify(n) } - val fn = expand(name) - if (name.isTypeName) fn.toTypeName else fn.toTermName + if name.isTypeName then fn.toTypeName else fn.toTermName } /** The encoded flat name of this denotation, where joined names are separated by `separator` characters. */