From 140a48f98499bb97f2dbc0cf40a2ecd02e5a8d43 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 1 Mar 2024 18:28:56 +0100 Subject: [PATCH 1/3] bugfix: emit as extension method if member of implicit class with val in constructor --- .../internal/mtags/ScalaToplevelMtags.scala | 28 +++++++++++++---- .../test/scala/tests/ScalaToplevelSuite.scala | 30 ++++++++++++++++++- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index 2e7484e05ed..5de3c81056d 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -290,12 +290,27 @@ class ScalaToplevelMtags( ) case DEF | VAL | VAR | GIVEN if expectTemplate.map(!_.isExtension).getOrElse(true) => + val isInParen = + region match { + case (_: Region.InParenCaseClass) | (_: Region.InParenClass) => + true + case _ => false + } + val isImplicit = + (isInParen && expectTemplate.exists( + _.isImplicit + )) || (!isInParen && region.isImplicit) if (needEmitTermMember()) { withOwner(currRegion.termOwner) { - emitTerm(currRegion) + emitTerm(currRegion, isImplicit) } } else scanner.nextToken() - loop(indent, isAfterNewline = false, currRegion, newExpectIgnoreBody) + loop( + indent, + isAfterNewline = false, + currRegion, + if (isInParen) expectTemplate else newExpectIgnoreBody + ) case TYPE if expectTemplate.map(!_.isExtension).getOrElse(true) => if (needEmitMember(currRegion) && !prevWasDot) { withOwner(currRegion.termOwner) { @@ -716,7 +731,8 @@ class ScalaToplevelMtags( /** * Enters a global element (def/val/var/given) */ - def emitTerm(region: Region): Unit = { + def emitTerm(region: Region, isParentImplicit: Boolean): Unit = { + val extensionProperty = if (isParentImplicit) EXTENSION else 0 val kind = scanner.curr.token acceptTrivia() kind match { @@ -726,7 +742,7 @@ class ScalaToplevelMtags( name.name, name.pos, Kind.METHOD, - SymbolInformation.Property.VAL.value + SymbolInformation.Property.VAL.value | extensionProperty ) resetRegion(region) }) @@ -736,7 +752,7 @@ class ScalaToplevelMtags( name.name, "()", name.pos, - SymbolInformation.Property.VAR.value + SymbolInformation.Property.VAR.value | extensionProperty ) resetRegion(region) }) @@ -746,7 +762,7 @@ class ScalaToplevelMtags( name.name, region.overloads.disambiguator(name.name), name.pos, - 0 + extensionProperty ) ) case GIVEN => diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index 90bf52ca242..31700522160 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -8,6 +8,7 @@ import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.mtags.Mtags import scala.meta.internal.mtags.ResolvedOverriddenSymbol import scala.meta.internal.mtags.UnresolvedOverriddenSymbol +import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.io.AbsolutePath import munit.TestOptions @@ -655,12 +656,38 @@ class ScalaToplevelSuite extends BaseSuite { mode = All, ) + check( + "implicit-class-with-val", + """|package a + |object Foo { + | implicit class IntOps(private val i: Int) extends AnyVal { + | def inc: Int = i + 1 + | } + |} + |""".stripMargin, + List( + "a/", "a/Foo.", "a/Foo.IntOps# -> AnyVal", "a/Foo.IntOps#i.", + "a/Foo.IntOps#inc().", + ), + mode = All, + additionalSymbolCheck = syms => + assert( + syms + .find(_.symbol == "a/Foo.IntOps#inc()") + .map(_.isExtension) + .getOrElse( + true + ) // if the symbol doesn't exit it should fail on a different assert + ), + ) + def check( options: TestOptions, code: String, expected: List[String], mode: Mode = Toplevel, dialect: Dialect = dialects.Scala3, + additionalSymbolCheck: Seq[SymbolInformation] => Unit = _ => (), )(implicit location: munit.Location): Unit = { test(options) { val dir = AbsolutePath(Files.createTempDirectory("mtags")) @@ -672,7 +699,8 @@ class ScalaToplevelSuite extends BaseSuite { val includeMembers = mode == All val (doc, overrides) = Mtags.indexWithOverrides(input, dialect, includeMembers) - val symbols = doc.occurrences.map(_.symbol).toList + additionalSymbolCheck(doc.symbols) + val symbols = doc.symbols.map(_.symbol).toList val overriddenMap = overrides.toMap symbols.map { symbol => overriddenMap.get(symbol) match { From 90b921de9251cc673d0312b1cb31d8a2cfad7fda Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 5 Mar 2024 14:24:27 +0100 Subject: [PATCH 2/3] account for refined types --- .../internal/mtags/ScalaToplevelMtags.scala | 50 ++++++++++------ tests/unit/src/test/scala/tests/Example.scala | 12 ++++ .../test/scala/tests/ScalaToplevelSuite.scala | 59 +++++++++++-------- 3 files changed, 78 insertions(+), 43 deletions(-) create mode 100644 tests/unit/src/test/scala/tests/Example.scala diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index 5de3c81056d..55df0ab642f 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -79,6 +79,13 @@ class ScalaToplevelMtags( case _ => region } + private def isInParenthesis(region: Region): Boolean = + region match { + case (_: Region.InParenCaseClass) | (_: Region.InParenClass) => + true + case _ => false + } + @tailrec private def loop( indent: Int, @@ -290,16 +297,12 @@ class ScalaToplevelMtags( ) case DEF | VAL | VAR | GIVEN if expectTemplate.map(!_.isExtension).getOrElse(true) => - val isInParen = - region match { - case (_: Region.InParenCaseClass) | (_: Region.InParenClass) => - true - case _ => false - } val isImplicit = - (isInParen && expectTemplate.exists( - _.isImplicit - )) || (!isInParen && region.isImplicit) + if (isInParenthesis(region)) + expectTemplate.exists( + _.isImplicit + ) + else region.isImplicit if (needEmitTermMember()) { withOwner(currRegion.termOwner) { emitTerm(currRegion, isImplicit) @@ -309,7 +312,7 @@ class ScalaToplevelMtags( indent, isAfterNewline = false, currRegion, - if (isInParen) expectTemplate else newExpectIgnoreBody + if (isInParenthesis(region)) expectTemplate else newExpectIgnoreBody ) case TYPE if expectTemplate.map(!_.isExtension).getOrElse(true) => if (needEmitMember(currRegion) && !prevWasDot) { @@ -423,15 +426,24 @@ class ScalaToplevelMtags( expectTemplate match { case Some(expect) if needToParseBody(expect) || needToParseExtension(expect) => - val next = - expect.startInBraceRegion( - currRegion, - expect.isExtension, - expect.isImplicit - ) - resetRegion(next) - scanner.nextToken() - loop(indent, isAfterNewline = false, next, None) + if (isInParenthesis(region)) { + // inside of a class constructor + // e.g. class A(val foo: Foo { type T = Int }) + // ^ + acceptBalancedDelimeters(LBRACE, RBRACE) + scanner.nextToken() + loop(indent, isAfterNewline = false, currRegion, expectTemplate) + } else { + val next = + expect.startInBraceRegion( + currRegion, + expect.isExtension, + expect.isImplicit + ) + resetRegion(next) + scanner.nextToken() + loop(indent, isAfterNewline = false, next, None) + } case _ => acceptBalancedDelimeters(LBRACE, RBRACE) scanner.nextToken() diff --git a/tests/unit/src/test/scala/tests/Example.scala b/tests/unit/src/test/scala/tests/Example.scala new file mode 100644 index 00000000000..bdb26b01fb6 --- /dev/null +++ b/tests/unit/src/test/scala/tests/Example.scala @@ -0,0 +1,12 @@ +package tests + +package a +object O { + trait Foo { + type T + } + + implicit class A(val foo: Foo { type T = Int }) { + def get: Int = 1 + } +} diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index 31700522160..cb0fa5e202e 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -8,7 +8,6 @@ import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.mtags.Mtags import scala.meta.internal.mtags.ResolvedOverriddenSymbol import scala.meta.internal.mtags.UnresolvedOverriddenSymbol -import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.io.AbsolutePath import munit.TestOptions @@ -350,8 +349,8 @@ class ScalaToplevelSuite extends BaseSuite { List( "a/", "a/A.", - "a/A.bar().", - "a/A.foo().", + "a/A.bar(). EXT", + "a/A.foo(). EXT", ), mode = All, dialect = dialects.Scala3, @@ -371,8 +370,8 @@ class ScalaToplevelSuite extends BaseSuite { List( "a/", "a/Test$package.", - "a/Test$package.bar().", - "a/Test$package.foo().", + "a/Test$package.bar(). EXT", + "a/Test$package.foo(). EXT", ), mode = All, dialect = dialects.Scala3, @@ -388,8 +387,8 @@ class ScalaToplevelSuite extends BaseSuite { | def baz: Long = ??? |""".stripMargin, List( - "a/", "a/Test$package.", "a/Test$package.foo().", "a/Test$package.bar().", - "a/Test$package.baz().", + "a/", "a/Test$package.", "a/Test$package.foo(). EXT", + "a/Test$package.bar(). EXT", "a/Test$package.baz(). EXT", ), mode = All, dialect = dialects.Scala3, @@ -656,6 +655,26 @@ class ScalaToplevelSuite extends BaseSuite { mode = All, ) + check( + "refined-type", + """|package a + |object O { + | trait Foo { + | type T + | } + | + | implicit class A(val foo: Foo { type T = Int }) { + | def get: Int = 1 + | } + |} + |""".stripMargin, + List( + "a/", "a/O.", "a/O.A#", "a/O.A#foo. EXT", "a/O.A#get(). EXT", "a/O.Foo#", + "a/O.Foo#T#", + ), + mode = All, + ) + check( "implicit-class-with-val", """|package a @@ -666,19 +685,10 @@ class ScalaToplevelSuite extends BaseSuite { |} |""".stripMargin, List( - "a/", "a/Foo.", "a/Foo.IntOps# -> AnyVal", "a/Foo.IntOps#i.", - "a/Foo.IntOps#inc().", + "a/", "a/Foo.", "a/Foo.IntOps# -> AnyVal", "a/Foo.IntOps#i. EXT", + "a/Foo.IntOps#inc(). EXT", ), mode = All, - additionalSymbolCheck = syms => - assert( - syms - .find(_.symbol == "a/Foo.IntOps#inc()") - .map(_.isExtension) - .getOrElse( - true - ) // if the symbol doesn't exit it should fail on a different assert - ), ) def check( @@ -687,7 +697,6 @@ class ScalaToplevelSuite extends BaseSuite { expected: List[String], mode: Mode = Toplevel, dialect: Dialect = dialects.Scala3, - additionalSymbolCheck: Seq[SymbolInformation] => Unit = _ => (), )(implicit location: munit.Location): Unit = { test(options) { val dir = AbsolutePath(Files.createTempDirectory("mtags")) @@ -699,12 +708,14 @@ class ScalaToplevelSuite extends BaseSuite { val includeMembers = mode == All val (doc, overrides) = Mtags.indexWithOverrides(input, dialect, includeMembers) - additionalSymbolCheck(doc.symbols) - val symbols = doc.symbols.map(_.symbol).toList + // additionalSymbolCheck(doc.symbols) + // val symbols = doc.symbols.map(_.symbol).toList val overriddenMap = overrides.toMap - symbols.map { symbol => + doc.symbols.map { symbolInfo => + val symbol = symbolInfo.symbol + val suffix = if (symbolInfo.isExtension) " EXT" else "" overriddenMap.get(symbol) match { - case None => symbol + case None => s"$symbol$suffix" case Some(symbols) => val overridden = symbols @@ -713,7 +724,7 @@ class ScalaToplevelSuite extends BaseSuite { case UnresolvedOverriddenSymbol(name) => name } .mkString(", ") - s"$symbol -> $overridden" + s"$symbol$suffix -> $overridden" } } case Toplevel => Mtags.topLevelSymbols(input, dialect) From 55f8766feefb8d7e0294aff2bab95c4ae8828650 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Wed, 6 Mar 2024 09:35:58 +0100 Subject: [PATCH 3/3] delete commented out code --- tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index cb0fa5e202e..38e763c55ea 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -708,8 +708,6 @@ class ScalaToplevelSuite extends BaseSuite { val includeMembers = mode == All val (doc, overrides) = Mtags.indexWithOverrides(input, dialect, includeMembers) - // additionalSymbolCheck(doc.symbols) - // val symbols = doc.symbols.map(_.symbol).toList val overriddenMap = overrides.toMap doc.symbols.map { symbolInfo => val symbol = symbolInfo.symbol