Skip to content

Commit

Permalink
Merge pull request #1801 from mrdziuban/print-lambda-and-match-types
Browse files Browse the repository at this point in the history
Support `LambdaType` and `MatchType`
  • Loading branch information
bjaglin committed Feb 17, 2024
2 parents 2f574d4 + 08c2edf commit 0c3e52d
Show file tree
Hide file tree
Showing 17 changed files with 308 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ object DocConstants {
val `{` = Doc.char('{')
val `}` = Doc.char('}')
val `=>` = Doc.text("=>")
val `=>>` = Doc.text("=>>")
val `()` = Doc.text("()")
val `*` = Doc.char('*')
val L: Doc = Doc.char('L')
Expand All @@ -29,4 +30,5 @@ object DocConstants {
val `extends`: Doc = Doc.text("extends")
val `with`: Doc = Doc.text("with")
val `forSome`: Doc = Doc.text("forSome")
val `match`: Doc = Doc.text("match")
}
16 changes: 16 additions & 0 deletions scalafix-core/src/main/scala/scalafix/internal/util/Pretty.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ object Pretty {
`=>` + Doc.space + pretty(t.tpe)
case t: RepeatedType =>
`*` + Doc.space + pretty(t.tpe)
case t: LambdaType =>
val tparams = Doc
.intercalate(Doc.comma, t.parameters.map(prettyTypeParameter))
.tightBracketBy(`[`, `]` + Doc.space + `=>>` + Doc.space)
tparams + pretty(t.returnType)
case t: MatchType =>
val cases = Doc
.intercalate(
Doc.comma + Doc.space,
t.cases.map { kase =>
pretty(kase.key) + Doc.space + `=>` +
Doc.space + pretty(kase.body)
}
)
.bracketBy(Doc.space + `match` + Doc.space + `{`, `}`)
normal(t.scrutinee) + cases
}
}
def normal(tpe: SemanticType): Doc = tpe match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,22 @@ class LegacyCodePrinter(doc: SemanticDocument) {
pprint(tpe)
case s.RepeatedType(tpe) =>
pprint(tpe)
case s.LambdaType(parameters, returnType) =>
parameters.foreach { scope =>
if (scope.symbols.nonEmpty) {
mkString("[", scope.symbols, "]")(emit)
text.append(" =>> ")
}
}
pprint(returnType)
case s.MatchType(scrutinee, cases) =>
pprint(scrutinee)
cases.foreach { case s.MatchType.CaseType(key, body) =>
text.append("case ")
pprint(key)
text.append(" => ")
pprint(body)
}
case s.NoType =>
}
private def pprint(const: s.Constant): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ final class SymtabFromProtobuf(symtab: Symtab) {
ExistentialType(tpe.convert, declarations.convert)
case s.UnionType(types) =>
UnionType(types.convert)
case s.LambdaType(parameters, returnType) =>
LambdaType(parameters.convert, returnType.convert)
case s.MatchType(scrutinee, cases) =>
MatchType(scrutinee.convert, cases.convert)
case s.NoType =>
NoType
}
Expand Down Expand Up @@ -132,4 +136,9 @@ final class SymtabFromProtobuf(symtab: Symtab) {
def convert: List[List[SymbolInformation]] =
scopes.iterator.map(s => sscope(Some(s))).toList
}

implicit class RichCases(cases: Seq[s.MatchType.CaseType]) {
def convert: List[CaseType] =
cases.iterator.map(c => CaseType(c.key.convert, c.body.convert)).toList
}
}
4 changes: 4 additions & 0 deletions scalafix-core/src/main/scala/scalafix/v1/SemanticType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ final case class ExistentialType(tpe: SemanticType, declarations: List[SymbolInf
final case class UniversalType(typeParameters: List[SymbolInformation], tpe: SemanticType) extends SemanticType
final case class ByNameType(tpe: SemanticType) extends SemanticType
final case class RepeatedType(tpe: SemanticType) extends SemanticType
final case class LambdaType(parameters: List[SymbolInformation], returnType: SemanticType) extends SemanticType
final case class MatchType(scrutinee: SemanticType, cases: List[CaseType]) extends SemanticType
case object NoType extends SemanticType

final case class CaseType(key: SemanticType, body: SemanticType)
14 changes: 14 additions & 0 deletions scalafix-tests/input/src/main/scala-3/test/TypeSignatures.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
rules = [
DecorateTypeSignatures
]
*/
package test

object TypeSignatures:

type MapKV[K] = [V] =>> Map[K, V]

type Convert[X] = X match
case String => Char
case Int => Float
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
[55:3..55:3]: test/T#X#`<init>`(). => primary ctor <init>(): X
[55:9..55:10]: test/T#X# => class X extends Object { self: X => +1 decls }
[56:7..56:8]: test/T#x. => val method xX
[59:8..59:12]: test/Test. => final object Test extends Object { self: Test.type => +10 decls }
[59:8..59:12]: test/Test. => final object Test extends Object { self: Test.type => +12 decls }
[60:9..60:10]: test/Test.M# => class M extends Object { self: M => +2 decls }
[61:5..61:5]: test/Test.M#`<init>`(). => primary ctor <init>(): M
[61:9..61:10]: test/Test.M#m(). => method m=> Int
Expand Down Expand Up @@ -127,4 +127,9 @@
[143:15..143:19]: test/Test.Literal.bool. => final val method booltrue
[144:15..144:19]: test/Test.Literal.unit. => final val method unitUnit
[145:15..145:23]: test/Test.Literal.javaEnum. => final val method javaEnumLinkOption
[146:15..146:22]: test/Test.Literal.clazzOf. => final val method clazzOfOption[Int]
[146:15..146:22]: test/Test.Literal.clazzOf. => final val method clazzOfOption[Int]
[149:8..149:13]: test/Test.MapKV# => type MapKV[K] = [V] =>> Map[K, V]
[149:17..149:18]: test/Test.MapKV#[K] => typeparam K
[149:25..149:26]: test/Test.MapKV#[V] => typeparam V
[151:8..151:15]: test/Test.Convert# => type Convert[X] = X match { String => Char, Int => Float }
[151:16..151:17]: test/Test.Convert#[X] => typeparam X
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ scalafix.test.CommentFileAtomic
scalafix.test.AddCommentEndOfFile
scalafix.test.ScalatestAutofixRule
scalafix.test.ExplicitSynthetic
scalafix.test.DecorateTypeSignatures
scalafix.test.cli.CrashingRule
scalafix.test.cli.NoOpRule
scalafix.test.cli.DeprecatedName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test

class LegacySyntheticsTest {
import scala.language.implicitConversions
implicit def conv[F[A], A](a: A): Throwable = ???
1: Throwable
}
154 changes: 154 additions & 0 deletions scalafix-tests/integration/src/main/scala-3/test/PrettyTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// scalafix:off
package test

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

class PrettyTest {
List(1).map(_ + 2)
for {
i <- 1.to(10)
j <- 2.to(20)
if i > i
} yield j
for {
i <- Future(1.to(10))
j <- Future(2.to(i.length))
} {
println(j)
}

class Inner
val a = new PrettyTest
val b = new a.Inner

Option(null.asInstanceOf[{
def bar(a: Int): Int
def foo(a: Int): Int
}
])
Option(b)
Option(null.asInstanceOf[Int with String])
Option(null.asInstanceOf[this.type])

}

import scala.language.existentials
import scala.language.higherKinds

class ann[T](x: T) extends scala.annotation.StaticAnnotation
class ann1 extends scala.annotation.StaticAnnotation
class ann2 extends scala.annotation.StaticAnnotation

class B

class C

class P {
class C
class X
val x = new X
}

class T {
class C
class X
val x = new X
}

object Test {
class M {
def m: Int = ???
}

trait N {
def n: Int = ???
}

class C extends M {
val p = new P
val x = p.x

val typeRef1: C = ???
val typeRef2: p.C = ???
val typeRef3: T#C = ???
val typeRef4: List[Int] = ???

val singleType1: x.type = ???
val singleType2: p.x.type = ???
val Either = scala.util.Either

val thisType1: this.type = ???
val thisType2: C.this.type = ???

val superType1 = super.m
val superType2 = super[M].m
val superType3 = C.super[M].m

val compoundType1: { def k: Int } = ???
val compoundType2: M with N = ???
val compoundType3: M with N { def k: Int } = ???
val compoundType4 = new { def k: Int = ??? }
val compoundType5 = new M with N
val compoundType6 = new M with N { def k: Int = ??? }

val annType1: T @ann(42) = ???
val annType2: T @ann1 @ann2 = ???

val existentialType2: List[_] = ???
val existentialType3 = Class.forName("foo.Bar")
val existentialType4 = Class.forName("foo.Bar")

def typeLambda1[M[_]] = ???
typeLambda1[({ type L[T] = List[T] })#L]

object ClassInfoType1
class ClassInfoType2 extends B { def x = 42 }
trait ClassInfoType3[T]

object MethodType {
def x1: Int = ???
def x2: Int = ???
def m3: Int = ???
def m4(): Int = ???
def m5(x: Int): Int = ???
def m6[T](x: T): T = ???
}

object ByNameType {
def m1(x: => Int): Int = ???
}

case class RepeatedType(s: String*) {
def m1(x: Int*): Int = s.length
}

object TypeType {
type T1
def m2[T2 >: C <: C] = ???
def m3[M3[_]] = ???
type T4 = C
type T5[U] = U
}
}

object Literal {
final val int = 1
final val long = 1L
final val float = 1f
final val double = 2d
final val nil = null
final val char = 'a'
final val string = "a"
final val bool = true
final val unit = ()
final val javaEnum = java.nio.file.LinkOption.NOFOLLOW_LINKS
final val clazzOf = classOf[Option[Int]]
}

type MapKV = [K] =>> [V] =>> Map[K, V]

type Convert[X] = X match
case String => Char
case Int => Float
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package scalafix.test

import scala.meta._

import scalafix.v1._

class DecorateTypeSignatures extends SemanticRule("DecorateTypeSignatures") {

override def fix(implicit doc: SemanticDocument): Patch =
doc.tree.collect { case t: Defn.Type =>
t.symbol.info
.map(_.signature)
.map {
case sig: TypeSignature =>
Patch.addLeft(t, "/*" + sig.upperBound.toString + "*/ ")
case _ =>
Patch.empty
}
.getOrElse(Patch.empty)
}.asPatch

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package scalafix.tests.v0
import scala.meta._
import scalafix.tests.core.BaseSemanticSuite

// LegacySyntheticsTest cannot be compiled with Scala 2.13 or Scala 3
class LegacySyntheticsSuite extends BaseSemanticSuite("LegacySyntheticsTest") {

test("text") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package scalafix.tests.v0

import scala.meta.*

import scalafix.tests.core.BaseSemanticSuite

class LegacySyntheticsSuite extends BaseSemanticSuite("LegacySyntheticsTest") {

test("text") {
val synthetics = index.synthetics(input)
val obtained = synthetics.sortBy(_.position.start).mkString("\n")
val expected =
"""
|[141..142): conv[[A] =>> Any, Int](*)
| [0..4): conv => test/LegacySyntheticsTest#conv().
| [6..7): A => test/LegacySyntheticsTest#conv().[F][A]
| [13..16): Any => scala/Any#
| [18..21): Int => scala/Int#
| [23..24): * => _star_.
""".stripMargin

assertNoDiff(obtained, expected)
}

test("parsable") {
val synthetics = index.synthetics(input)
synthetics.foreach(n => n.text.parse[Term])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import scala.meta.internal.symtab.SymbolTable

import org.scalatest.BeforeAndAfterAll
import org.scalatest.funsuite.AnyFunSuite
import scalafix.Versions
import scalafix.internal.config.ScalaVersion
import scalafix.internal.reflect.ClasspathOps
import scalafix.internal.v0.LegacyInMemorySemanticdbIndex
Expand All @@ -16,15 +17,28 @@ import scalafix.v1.SemanticDocument
import scalafix.v1.SyntacticDocument

object BaseSemanticSuite {
val scalaVersion = ScalaVersion.scala2 // For tests
val defaultScalaVersion = ScalaVersion.scala2 // For tests
lazy val symtab: SymbolTable = {
val classpath =
Classpaths.withDirectory(AbsolutePath(BuildInfo.classDirectory))
ClasspathOps.newSymbolTable(classpath)
}
def loadDoc(filename: String): SemanticDocument = {
val root = AbsolutePath(BuildInfo.sourceroot).resolve("scala/test")
val abspath = root.resolve(filename)
def loadDoc(
filename: String,
scalaVersion: Option[String] = None
): SemanticDocument = {
val (abspath, scalaVersion) = {
val root = AbsolutePath(BuildInfo.sourceroot)
val commonPath = root.resolve("scala/test").resolve(filename)
if (commonPath.toFile.exists) {
(commonPath, defaultScalaVersion)
} else {
val scalaMajorVersion = Versions.scalaVersion.split("\\.")(0)
val path =
root.resolve(s"scala-$scalaMajorVersion/test").resolve(filename)
(path, ScalaVersion.from(Versions.scalaVersion).get)
}
}
val relpath = abspath.toRelative(AbsolutePath(BuildInfo.baseDirectory))
val input = Input.File(abspath)
val doc = SyntacticDocument.fromInput(input, scalaVersion)
Expand Down
Loading

0 comments on commit 0c3e52d

Please sign in to comment.