From 68e720ced5685fdc26c7d887c33c1c3aed269e15 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sun, 5 Mar 2023 19:12:47 +0100 Subject: [PATCH 1/4] Support records in JavaParsers This is a port of scala/scala#9551 --- .../src/dotty/tools/dotc/core/StdNames.scala | 7 ++ .../tools/dotc/parsing/JavaParsers.scala | 82 +++++++++++++++++-- .../dotty/tools/dotc/parsing/JavaTokens.scala | 3 + tests/pos/java-records/FromScala.scala | 15 ++++ tests/pos/java-records/R1.java | 9 ++ 5 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 tests/pos/java-records/FromScala.scala create mode 100644 tests/pos/java-records/R1.java diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 27e97a92b48e..c54a60f5a8fa 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -204,6 +204,7 @@ object StdNames { final val Null: N = "Null" final val Object: N = "Object" final val FromJavaObject: N = "" + final val Record: N = "Record" final val Product: N = "Product" final val PartialFunction: N = "PartialFunction" final val PrefixType: N = "PrefixType" @@ -912,6 +913,10 @@ object StdNames { final val VOLATILEkw: N = kw("volatile") final val WHILEkw: N = kw("while") + final val RECORDid: N = "record" + final val VARid: N = "var" + final val YIELDid: N = "yield" + final val BoxedBoolean: N = "java.lang.Boolean" final val BoxedByte: N = "java.lang.Byte" final val BoxedCharacter: N = "java.lang.Character" @@ -944,6 +949,8 @@ object StdNames { final val JavaSerializable: N = "java.io.Serializable" } + + class JavaTermNames extends JavaNames[TermName] { protected def fromString(s: String): TermName = termName(s) } diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index daeebcbcc17c..71aeee05fe10 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -20,7 +20,9 @@ import StdNames._ import reporting._ import dotty.tools.dotc.util.SourceFile import util.Spans._ + import scala.collection.mutable.ListBuffer +import scala.collection.immutable.ListMap object JavaParsers { @@ -96,8 +98,12 @@ object JavaParsers { def javaLangDot(name: Name): Tree = Select(javaDot(nme.lang), name) + /** Tree representing `java.lang.Object` */ def javaLangObject(): Tree = javaLangDot(tpnme.Object) + /** Tree representing `java.lang.Record` */ + def javaLangRecord(): Tree = javaLangDot(tpnme.Record) + def arrayOf(tpt: Tree): AppliedTypeTree = AppliedTypeTree(scalaDot(tpnme.Array), List(tpt)) @@ -555,6 +561,14 @@ object JavaParsers { def definesInterface(token: Int): Boolean = token == INTERFACE || token == AT + /** If the next token is the identifier "record", convert it into the RECORD token. + * This makes it easier to handle records in various parts of the code, + * in particular when a `parentToken` is passed to some functions. + */ + def adaptRecordIdentifier(): Unit = + if in.token == IDENTIFIER && in.name == jnme.RECORDid then + in.token = RECORD + def termDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = { val inInterface = definesInterface(parentToken) val tparams = if (in.token == LT) typeParams(Flags.JavaDefined | Flags.Param) else List() @@ -581,6 +595,16 @@ object JavaParsers { TypeTree(), methodBody()).withMods(mods) } } + } else if (in.token == LBRACE && rtptName != nme.EMPTY && parentToken == RECORD) { + /* + record RecordName(T param1, ...) { + RecordName { // <- here + // methodBody + } + } + */ + methodBody() + Nil } else { var mods1 = mods @@ -717,12 +741,11 @@ object JavaParsers { ValDef(name, tpt2, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1) } - def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match { - case CLASS | ENUM | INTERFACE | AT => - typeDecl(start, if (definesInterface(parentToken)) mods | Flags.JavaStatic else mods) + def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match + case CLASS | ENUM | RECORD | INTERFACE | AT => + typeDecl(start, if definesInterface(parentToken) then mods | Flags.JavaStatic else mods) case _ => termDecl(start, mods, parentToken, parentTParams) - } def makeCompanionObject(cdef: TypeDef, statics: List[Tree]): Tree = atSpan(cdef.span) { @@ -804,6 +827,49 @@ object JavaParsers { addCompanionObject(statics, cls) } + def recordDecl(start: Offset, mods: Modifiers): List[Tree] = + accept(RECORD) + val nameOffset = in.offset + val name = identForType() + val tparams = typeParams() + val header = formalParams() + val superclass = javaLangRecord() // records always extend java.lang.Record + val interfaces = interfacesOpt() // records may implement interfaces + val (statics, body) = typeBody(RECORD, name, tparams) + + // We need to generate accessors for every param, if no method with the same name is already defined + + var fieldsByName = header.map(v => (v.name, (v.tpt, v.mods.annotations))).to(ListMap) + + for case DefDef(name, paramss, _, _) <- body + if paramss.isEmpty && fieldsByName.contains(name) + do + fieldsByName -= name + end for + + val accessors = + (for (name, (tpt, annots)) <- fieldsByName yield + DefDef(name, Nil, tpt, unimplementedExpr) + .withMods(Modifiers(Flags.JavaDefined | Flags.Method | Flags.Synthetic)) + ).toList + + // generate the canonical constructor + val canonicalConstructor = makeConstructor(header, tparams) + + // return the trees + val recordTypeDef = atSpan(start, nameOffset) { + TypeDef(name, + makeTemplate( + parents = superclass :: interfaces, + stats = canonicalConstructor :: accessors ::: body, + tparams = tparams, + false + ) + ) + } + addCompanionObject(statics, recordTypeDef) + end recordDecl + def interfaceDecl(start: Offset, mods: Modifiers): List[Tree] = { accept(INTERFACE) val nameOffset = in.offset @@ -846,7 +912,8 @@ object JavaParsers { else if (in.token == SEMI) in.nextToken() else { - if (in.token == ENUM || definesInterface(in.token)) mods |= Flags.JavaStatic + adaptRecordIdentifier() + if (in.token == ENUM || in.token == RECORD || definesInterface(in.token)) mods |= Flags.JavaStatic val decls = memberDecl(start, mods, parentToken, parentTParams) (if (mods.is(Flags.JavaStatic) || inInterface && !(decls exists (_.isInstanceOf[DefDef]))) statics @@ -947,13 +1014,13 @@ object JavaParsers { } } - def typeDecl(start: Offset, mods: Modifiers): List[Tree] = in.token match { + def typeDecl(start: Offset, mods: Modifiers): List[Tree] = in.token match case ENUM => enumDecl(start, mods) case INTERFACE => interfaceDecl(start, mods) case AT => annotationDecl(start, mods) case CLASS => classDecl(start, mods) + case RECORD => recordDecl(start, mods) case _ => in.nextToken(); syntaxError(em"illegal start of type declaration", skipIt = true); List(errorTypeTree) - } def tryConstant: Option[Constant] = { val negate = in.token match { @@ -1004,6 +1071,7 @@ object JavaParsers { if (in.token != EOF) { val start = in.offset val mods = modifiers(inInterface = false) + adaptRecordIdentifier() // needed for typeDecl buf ++= typeDecl(start, mods) } } diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala b/compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala index 3e73b6d95adb..2b7882173e00 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala @@ -41,6 +41,9 @@ object JavaTokens extends TokensCommon { inline val SWITCH = 133; enter(SWITCH, "switch") inline val ASSERT = 134; enter(ASSERT, "assert") + /** contextual keywords (turned into keywords in certain conditions, see JLS 3.9 of Java 9+) */ + inline val RECORD = 135; enter(RECORD, "record") + /** special symbols */ inline val EQEQ = 140 inline val BANGEQ = 141 diff --git a/tests/pos/java-records/FromScala.scala b/tests/pos/java-records/FromScala.scala new file mode 100644 index 000000000000..d9b9f4d2e129 --- /dev/null +++ b/tests/pos/java-records/FromScala.scala @@ -0,0 +1,15 @@ +object C: + def useR1 = + // constructor signature + val r = R1(123, "hello") + + // accessors + val i: Int = r.i + val s: String = r.s + + // methods + val iRes: Int = r.getInt() + val sRes: String = r.getString() + + // supertype + val record: java.lang.Record = r diff --git a/tests/pos/java-records/R1.java b/tests/pos/java-records/R1.java new file mode 100644 index 000000000000..832d288547ab --- /dev/null +++ b/tests/pos/java-records/R1.java @@ -0,0 +1,9 @@ +public record R1(int i, String s) { + public String getString() { + return s + i; + } + + public int getInt() { + return 0; + } +} From ed126d9657c8eb4886bee3bb2e05310c8f41a8ef Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Tue, 7 Mar 2023 17:46:27 +0100 Subject: [PATCH 2/4] Fix record's constructor Co-authored-by: Guillaume Martres --- compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala | 5 +++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 71aeee05fe10..5ab187871542 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -854,7 +854,8 @@ object JavaParsers { ).toList // generate the canonical constructor - val canonicalConstructor = makeConstructor(header, tparams) + val canonicalConstructor = + DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(header)), TypeTree(), EmptyTree).withMods(Modifiers(Flags.JavaDefined, mods.privateWithin)) // return the trees val recordTypeDef = atSpan(start, nameOffset) { @@ -863,7 +864,7 @@ object JavaParsers { parents = superclass :: interfaces, stats = canonicalConstructor :: accessors ::: body, tparams = tparams, - false + true ) ) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 7eb8519739c6..02df12ecc0bc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3589,7 +3589,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer adapt(tree, pt, ctx.typerState.ownedVars) private def adapt1(tree: Tree, pt: Type, locked: TypeVars)(using Context): Tree = { - assert(pt.exists && !pt.isInstanceOf[ExprType] || ctx.reporter.errorsReported) + assert(pt.exists && !pt.isInstanceOf[ExprType] || ctx.reporter.errorsReported, i"tree: $tree, pt: $pt") def methodStr = err.refStr(methPart(tree).tpe) def readapt(tree: Tree)(using Context) = adapt(tree, pt, locked) From d1df3eed906ffd5b79a0d4fb0051d6dad7e92ff2 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Tue, 7 Mar 2023 18:08:51 +0100 Subject: [PATCH 3/4] Add more record tests and only run them on java >= 16 Co-authored-by: Guillaume Martres --- .../dotty/tools/dotc/CompilationTests.scala | 11 +++-- .../pos-java16+/java-records/FromScala.scala | 43 +++++++++++++++++++ tests/pos-java16+/java-records/IntLike.scala | 2 + .../{pos => pos-java16+}/java-records/R1.java | 0 tests/pos-java16+/java-records/R2.java | 13 ++++++ tests/pos-java16+/java-records/R3.java | 22 ++++++++++ tests/pos/java-records/FromScala.scala | 15 ------- 7 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 tests/pos-java16+/java-records/FromScala.scala create mode 100644 tests/pos-java16+/java-records/IntLike.scala rename tests/{pos => pos-java16+}/java-records/R1.java (100%) create mode 100644 tests/pos-java16+/java-records/R2.java create mode 100644 tests/pos-java16+/java-records/R3.java delete mode 100644 tests/pos/java-records/FromScala.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index c782c78f8eb7..8107e1579525 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -29,7 +29,7 @@ class CompilationTests { @Test def pos: Unit = { implicit val testGroup: TestGroup = TestGroup("compilePos") - aggregateTests( + var tests = List( compileFile("tests/pos/nullarify.scala", defaultOptions.and("-Ycheck:nullarify")), compileFile("tests/pos-special/utf8encoded.scala", explicitUTF8), compileFile("tests/pos-special/utf16encoded.scala", explicitUTF16), @@ -65,8 +65,13 @@ class CompilationTests { compileFile("tests/pos-special/extend-java-enum.scala", defaultOptions.and("-source", "3.0-migration")), compileFile("tests/pos-custom-args/help.scala", defaultOptions.and("-help", "-V", "-W", "-X", "-Y")), compileFile("tests/pos-custom-args/i13044.scala", defaultOptions.and("-Xmax-inlines:33")), - compileFile("tests/pos-custom-args/jdk-8-app.scala", defaultOptions.and("-release:8")), - ).checkCompile() + compileFile("tests/pos-custom-args/jdk-8-app.scala", defaultOptions.and("-release:8")) + ) + + if scala.util.Properties.isJavaAtLeast("16") then + tests ::= compileFilesInDir("tests/pos-java16+", defaultOptions.and("-Ysafe-init")) + + aggregateTests(tests*).checkCompile() } @Test def rewrites: Unit = { diff --git a/tests/pos-java16+/java-records/FromScala.scala b/tests/pos-java16+/java-records/FromScala.scala new file mode 100644 index 000000000000..67747e658432 --- /dev/null +++ b/tests/pos-java16+/java-records/FromScala.scala @@ -0,0 +1,43 @@ +object C: + def useR1: Unit = + // constructor signature + val r = R1(123, "hello") + + // accessors + val i: Int = r.i + val s: String = r.s + + // methods + val iRes: Int = r.getInt() + val sRes: String = r.getString() + + // supertype + val record: java.lang.Record = r + + def useR2: Unit = + // constructor signature + val r2 = R2.R(123, "hello") + + // accessors signature + val i: Int = r2.i + val s: String = r2.s + + // method + val i2: Int = r2.getInt + + // supertype + val isIntLike: IntLike = r2 + val isRecord: java.lang.Record = r2 + + def useR3 = + // constructor signature + val r3 = R3(123, 42L, "hi") + new R3("hi", 123) + // accessors signature + val i: Int = r3.i + val l: Long = r3.l + val s: String = r3.s + // method + val l2: Long = r3.l(43L, 44L) + // supertype + val isRecord: java.lang.Record = r3 diff --git a/tests/pos-java16+/java-records/IntLike.scala b/tests/pos-java16+/java-records/IntLike.scala new file mode 100644 index 000000000000..1f760018a975 --- /dev/null +++ b/tests/pos-java16+/java-records/IntLike.scala @@ -0,0 +1,2 @@ +trait IntLike: + def getInt: Int diff --git a/tests/pos/java-records/R1.java b/tests/pos-java16+/java-records/R1.java similarity index 100% rename from tests/pos/java-records/R1.java rename to tests/pos-java16+/java-records/R1.java diff --git a/tests/pos-java16+/java-records/R2.java b/tests/pos-java16+/java-records/R2.java new file mode 100644 index 000000000000..01da13d83b65 --- /dev/null +++ b/tests/pos-java16+/java-records/R2.java @@ -0,0 +1,13 @@ +public class R2 { + final record R(int i, String s) implements IntLike { + public int getInt() { + return i; + } + + // Canonical constructor + // public R(int i, java.lang.String s) { + // this.i = i; + // this.s = s.intern(); + // } + } +} diff --git a/tests/pos-java16+/java-records/R3.java b/tests/pos-java16+/java-records/R3.java new file mode 100644 index 000000000000..616481a0ae1f --- /dev/null +++ b/tests/pos-java16+/java-records/R3.java @@ -0,0 +1,22 @@ +public record R3(int i, long l, String s) { + + // User-specified accessor + public int i() { + return i + 1; // evil >:) + } + + // Not an accessor - too many parameters + public long l(long a1, long a2) { + return a1 + a2; + } + + // Secondary constructor + public R3(String s, int i) { + this(i, 42L, s); + } + + // Compact constructor + public R3 { + s = s.intern(); + } +} \ No newline at end of file diff --git a/tests/pos/java-records/FromScala.scala b/tests/pos/java-records/FromScala.scala deleted file mode 100644 index d9b9f4d2e129..000000000000 --- a/tests/pos/java-records/FromScala.scala +++ /dev/null @@ -1,15 +0,0 @@ -object C: - def useR1 = - // constructor signature - val r = R1(123, "hello") - - // accessors - val i: Int = r.i - val s: String = r.s - - // methods - val iRes: Int = r.getInt() - val sRes: String = r.getString() - - // supertype - val record: java.lang.Record = r From da4996a132e2d44a4630c5a17173f468173333e3 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Tue, 7 Mar 2023 18:54:53 +0100 Subject: [PATCH 4/4] Remove synthetic record constructor if the user has written one manually Co-authored-by: Guillaume Martres --- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../dotty/tools/dotc/parsing/JavaParsers.scala | 10 +++++----- .../src/dotty/tools/dotc/typer/Namer.scala | 18 +++++++++++++----- .../src/dotty/tools/dotc/typer/Typer.scala | 12 +++++++++--- tests/pos-java16+/java-records/R2.java | 8 ++++---- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b7211b3ce5e3..a4f27358f673 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -688,6 +688,7 @@ class Definitions { @tu lazy val JavaCalendarClass: ClassSymbol = requiredClass("java.util.Calendar") @tu lazy val JavaDateClass: ClassSymbol = requiredClass("java.util.Date") @tu lazy val JavaFormattableClass: ClassSymbol = requiredClass("java.util.Formattable") + @tu lazy val JavaRecordClass: Symbol = getClassIfDefined("java.lang.Record") @tu lazy val JavaEnumClass: ClassSymbol = { val cls = requiredClass("java.lang.Enum") diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 5ab187871542..6ec896dcb200 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -21,8 +21,7 @@ import reporting._ import dotty.tools.dotc.util.SourceFile import util.Spans._ -import scala.collection.mutable.ListBuffer -import scala.collection.immutable.ListMap +import scala.collection.mutable.{ListBuffer, LinkedHashMap} object JavaParsers { @@ -839,7 +838,7 @@ object JavaParsers { // We need to generate accessors for every param, if no method with the same name is already defined - var fieldsByName = header.map(v => (v.name, (v.tpt, v.mods.annotations))).to(ListMap) + var fieldsByName = header.map(v => (v.name, (v.tpt, v.mods.annotations))).to(LinkedHashMap) for case DefDef(name, paramss, _, _) <- body if paramss.isEmpty && fieldsByName.contains(name) @@ -855,7 +854,8 @@ object JavaParsers { // generate the canonical constructor val canonicalConstructor = - DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(header)), TypeTree(), EmptyTree).withMods(Modifiers(Flags.JavaDefined, mods.privateWithin)) + DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(header)), TypeTree(), EmptyTree) + .withMods(Modifiers(Flags.JavaDefined | Flags.Synthetic, mods.privateWithin)) // return the trees val recordTypeDef = atSpan(start, nameOffset) { @@ -866,7 +866,7 @@ object JavaParsers { tparams = tparams, true ) - ) + ).withMods(mods) } addCompanionObject(statics, recordTypeDef) end recordDecl diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 4eeb5540f137..cc4433f75a68 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -862,7 +862,6 @@ class Namer { typer: Typer => * with a user-defined method in the same scope with a matching type. */ private def invalidateIfClashingSynthetic(denot: SymDenotation): Unit = - def isCaseClassOrCompanion(owner: Symbol) = owner.isClass && { if (owner.is(Module)) owner.linkedClass.is(CaseClass) @@ -879,10 +878,19 @@ class Namer { typer: Typer => !sd.symbol.is(Deferred) && sd.matches(denot))) val isClashingSynthetic = - denot.is(Synthetic, butNot = ConstructorProxy) - && desugar.isRetractableCaseClassMethodName(denot.name) - && isCaseClassOrCompanion(denot.owner) - && (definesMember || inheritsConcreteMember) + denot.is(Synthetic, butNot = ConstructorProxy) && + ( + (desugar.isRetractableCaseClassMethodName(denot.name) + && isCaseClassOrCompanion(denot.owner) + && (definesMember || inheritsConcreteMember) + ) + || + // remove synthetic constructor of a java Record if it clashes with a non-synthetic constructor + (denot.isConstructor + && denot.owner.is(JavaDefined) && denot.owner.derivesFrom(defn.JavaRecordClass) + && denot.owner.unforcedDecls.lookupAll(denot.name).exists(c => c != denot.symbol && c.info.matches(denot.info)) + ) + ) if isClashingSynthetic then typr.println(i"invalidating clashing $denot in ${denot.owner}") diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 02df12ecc0bc..49b26122d15e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2433,11 +2433,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = { - if (!sym.info.exists) { // it's a discarded synthetic case class method, drop it - assert(sym.is(Synthetic) && desugar.isRetractableCaseClassMethodName(sym.name)) + def canBeInvalidated(sym: Symbol): Boolean = + sym.is(Synthetic) + && (desugar.isRetractableCaseClassMethodName(sym.name) || + (sym.isConstructor && sym.owner.derivesFrom(defn.JavaRecordClass))) + + if !sym.info.exists then + // it's a discarded method (synthetic case class method or synthetic java record constructor), drop it + assert(canBeInvalidated(sym)) sym.owner.info.decls.openForMutations.unlink(sym) return EmptyTree - } + // TODO: - Remove this when `scala.language.experimental.erasedDefinitions` is no longer experimental. // - Modify signature to `erased def erasedValue[T]: T` if sym.eq(defn.Compiletime_erasedValue) then diff --git a/tests/pos-java16+/java-records/R2.java b/tests/pos-java16+/java-records/R2.java index 01da13d83b65..4b3f881628b9 100644 --- a/tests/pos-java16+/java-records/R2.java +++ b/tests/pos-java16+/java-records/R2.java @@ -5,9 +5,9 @@ public int getInt() { } // Canonical constructor - // public R(int i, java.lang.String s) { - // this.i = i; - // this.s = s.intern(); - // } + public R(int i, java.lang.String s) { + this.i = i; + this.s = s.intern(); + } } }