diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 2b3806e48f79..0e9ccecb18c8 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -369,6 +369,7 @@ object NameKinds { val FieldName: SuffixNameKind = new SuffixNameKind(FIELD, "$$local") { override def mkString(underlying: TermName, info: ThisInfo) = underlying.toString } + val ExplicitFieldName: SuffixNameKind = new SuffixNameKind(EXPLICITFIELD, "$field") val ExtMethName: SuffixNameKind = new SuffixNameKind(EXTMETH, "$extension") val ParamAccessorName: SuffixNameKind = new SuffixNameKind(PARAMACC, "$accessor") val ModuleClassName: SuffixNameKind = new SuffixNameKind(OBJECTCLASS, "$", optInfoString = "ModuleClass") diff --git a/compiler/src/dotty/tools/dotc/core/NameTags.scala b/compiler/src/dotty/tools/dotc/core/NameTags.scala index 5628adbd6f89..59dfaa3d437b 100644 --- a/compiler/src/dotty/tools/dotc/core/NameTags.scala +++ b/compiler/src/dotty/tools/dotc/core/NameTags.scala @@ -37,6 +37,9 @@ object NameTags extends TastyFormat.NameTags { final val AVOIDLOWER = 36 final val AVOIDBOTH = 37 + inline val EXPLICITFIELD = 38 // An explicitly named field, introduce to avoid a clash + // with a regular field of the underlying name + def nameTagToString(tag: Int): String = tag match { case UTF8 => "UTF8" case QUALIFIED => "QUALIFIED" diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index b5b8ae7cd3e4..86a7232e3d8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -9,7 +9,9 @@ import dotty.tools.dotc.core.StdNames._ import ast._ import Trees._ import Flags._ +import Names.Name import NameOps._ +import NameKinds.{FieldName, ExplicitFieldName} import SymUtils._ import Symbols._ import Decorators._ @@ -249,7 +251,28 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = splitStats(stats1) case Nil => } + + /** Check that we do not have both a private field with name `x` and a private field + * with name `FieldName(x)`. These will map to the same JVM name and therefore cause + * a duplicate field error. If that case arises (as in i13862.scala), use an explicit + * name `x$field` instead of `FieldName(x). + */ + def checkNoFieldClashes() = + val fieldNames = mutable.HashSet[Name]() + for case field: ValDef <- clsStats do + field.symbol.name match + case FieldName(_) => + case name => fieldNames += name + for case field: ValDef <- clsStats do + field.symbol.name match + case fldName @ FieldName(name) if fieldNames.contains(name) => + val newName = ExplicitFieldName(name) + report.log(i"avoid field/field conflict by renaming $fldName to $newName") + field.symbol.copySymDenotation(name = newName).installAfter(thisPhase) + case _ => + splitStats(tree.body) + checkNoFieldClashes() // The initializers for the retained accessors */ val copyParams = accessors flatMap { acc => diff --git a/tests/run/i13862.scala b/tests/run/i13862.scala new file mode 100644 index 000000000000..4577f65e50d9 --- /dev/null +++ b/tests/run/i13862.scala @@ -0,0 +1,11 @@ +trait Foo(val num: Int) // a trait with a parameter stored in a val + +class Bar(num: Int) extends Foo(num): // an extending class with a parameter of the same name + def bar = this.num // implicitly creates another num in Bar + +@main def Test = Bar(123) + +class Bar2(n: Int) extends Foo(n): // an extending class with a parameter of the same name + private val num = n + def bar = this.num // implicitly creates another num in Bar +