Repository: scalameta/scalameta
Pull request #2742 (2022-04-26 16:09:48)

Don't throw Invariant exception when extending .type
Fixes https://github.com/scalameta/scalameta/issues/2741


Patch:
diff --git a/scalameta/dialects/shared/src/main/scala/scala/meta/Dialect.scala b/scalameta/dialects/shared/src/main/scala/scala/meta/Dialect.scala
index 4a1eeadb7..21b4c9b35 100644
--- a/scalameta/dialects/shared/src/main/scala/scala/meta/Dialect.scala
+++ b/scalameta/dialects/shared/src/main/scala/scala/meta/Dialect.scala
@@ -150,9 +150,7 @@ final class Dialect private (
     // import a.b.c.{ given, _} used for -X:source3
     val allowGivenImports: Boolean,
     // Scala 3 uses proper precedence rules for infix types, unlike Scala 2
-    val useInfixTypePrecedence: Boolean,
-    // Scala213Source3 and Scala3 allow infix operator being placed after nl
-    val allowInfixOperatorAfterNL: Boolean
+    val useInfixTypePrecedence: Boolean
 ) extends Product with Serializable {
 
   // NOTE(olafur) checklist for adding a new dialect field in a binary compatible way:
@@ -241,8 +239,7 @@ final class Dialect private (
       allowPlusMinusUnderscoreAsIdent = false,
       allowPlusMinusUnderscoreAsPlaceholder = false,
       allowGivenImports = false,
-      useInfixTypePrecedence = false,
-      allowInfixOperatorAfterNL = false
+      useInfixTypePrecedence = false
       // NOTE(olafur): declare the default value for new fields above this comment.
     )
   }
@@ -432,10 +429,6 @@ final class Dialect private (
     privateCopy(useInfixTypePrecedence = newValue)
   }
 
-  def withAllowInfixOperatorAfterNL(newValue: Boolean): Dialect = {
-    privateCopy(allowInfixOperatorAfterNL = newValue)
-  }
-
   // NOTE(olafur): add the next `withX()` method above this comment. Please try
   // to use consistent formatting, use `newValue` as the parameter name and wrap
   // the body inside curly braces.
@@ -498,8 +491,8 @@ final class Dialect private (
       allowPlusMinusUnderscoreAsIdent: Boolean = this.allowPlusMinusUnderscoreAsIdent,
       allowPlusMinusUnderscoreAsPlaceholder: Boolean = this.allowPlusMinusUnderscoreAsPlaceholder,
       allowGivenImports: Boolean = this.allowGivenImports,
-      useInfixTypePrecedence: Boolean = this.useInfixTypePrecedence,
-      allowInfixOperatorAfterNL: Boolean = this.allowInfixOperatorAfterNL
+      useInfixTypePrecedence: Boolean = this.useInfixTypePrecedence
+
       // NOTE(olafur): add the next parameter above this comment.
   ): Dialect = {
     new Dialect(
@@ -559,8 +552,7 @@ final class Dialect private (
       allowPlusMinusUnderscoreAsIdent,
       allowPlusMinusUnderscoreAsPlaceholder,
       allowGivenImports,
-      useInfixTypePrecedence,
-      allowInfixOperatorAfterNL
+      useInfixTypePrecedence
       // NOTE(olafur): add the next argument above this comment.
     )
   }
diff --git a/scalameta/dialects/shared/src/main/scala/scala/meta/dialects/package.scala b/scalameta/dialects/shared/src/main/scala/scala/meta/dialects/package.scala
index 485239891..ae60af29c 100644
--- a/scalameta/dialects/shared/src/main/scala/scala/meta/dialects/package.scala
+++ b/scalameta/dialects/shared/src/main/scala/scala/meta/dialects/package.scala
@@ -65,7 +65,6 @@ package object dialects {
     .withAllowPostfixStarVarargSplices(true)
     .withAllowPlusMinusUnderscoreAsIdent(true)
     .withAllowGivenImports(true)
-    .withAllowInfixOperatorAfterNL(true)
 
   /**
    * Dialect starting with Scala 2.12.14 for `-Xsource:3` option
@@ -146,7 +145,6 @@ package object dialects {
     .withAllowDoWhile(false)
     .withAllowPlusMinusUnderscoreAsPlaceholder(true)
     .withUseInfixTypePrecedence(true)
-    .withAllowInfixOperatorAfterNL(true)
 
   @deprecated("Use Scala3 instead", "4.4.2")
   implicit val Dotty = Scala3
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/LazyTokenIterator.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/LazyTokenIterator.scala
index 3f410211f..07ecf9098 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/LazyTokenIterator.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/LazyTokenIterator.scala
@@ -360,7 +360,7 @@ private[parsers] class LazyTokenIterator private (
           case _: RegionIndent | _: RegionIndentEnum => true
           case x: RegionParen => x.canProduceLF
           case _ => false
-        } && !next.isLeadingInfixOperator
+        }
       }
 
       def getIfCanProduceLF =
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
index b71f5c357..8c7df1685 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
@@ -3358,7 +3358,9 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
 
     def parents() = {
       val parents = ListBuffer[Init](
-        autoEndPos(decltype)(initRest(decltype, allowArgss = true, allowBraces = false))
+        autoEndPos(decltype)(
+          initRest(decltype, allowArgss = true, allowBraces = false, allowTypeSingleton = false)
+        )
       )
       while (token.is[KwWith] && tryAhead[Ident]) parents += init()
       parents.toList
@@ -3777,15 +3779,20 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       val t = autoPos(Term.This(autoPos { accept[KwThis]; Name.Anonymous() }))
       copyPos(t)(Type.Singleton(t))
     }
-    initRest(tpe, allowArgss = true, allowBraces = true)
+    initRest(tpe, allowArgss = true, allowBraces = true, allowTypeSingleton = true)
   }
 
   def initInsideAnnotation(allowArgss: Boolean): Init = {
-    initRest(exprSimpleType(), allowArgss = allowArgss, allowBraces = false)
+    initRest(
+      exprSimpleType(),
+      allowArgss = allowArgss,
+      allowBraces = false,
+      allowTypeSingleton = true
+    )
   }
 
   def initInsideTemplate(): Init = {
-    initRest(startModType(), allowArgss = true, allowBraces = false)
+    initRest(startModType(), allowArgss = true, allowBraces = false, allowTypeSingleton = false)
   }
 
   def quasiquoteInit(): Init = entrypointInit()
@@ -3797,7 +3804,12 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
     }
   }
 
-  def initRest(typeParser: => Type, allowArgss: Boolean, allowBraces: Boolean): Init = autoPos {
+  def initRest(
+      typeParser: => Type,
+      allowArgss: Boolean,
+      allowBraces: Boolean,
+      allowTypeSingleton: Boolean
+  ): Init = autoPos {
     def isPendingArglist = token.is[LeftParen] || (token.is[LeftBrace] && allowBraces)
     def newlineOpt() = {
       if (dialect.allowSignificantIndentation)
@@ -3809,6 +3821,8 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
         unquote[Init](t)
       case _ =>
         val tpe = typeParser
+        if (!allowTypeSingleton && tpe.is[Type.Singleton])
+          syntaxError(s"class type required but ${tpe.toString()} found", at = tpe.pos)
         val name = autoPos(Name.Anonymous())
         val argss = mutable.ListBuffer[List[Term]]()
         @inline def body() = {
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
index 0bf847247..efc972a6d 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
@@ -106,7 +106,7 @@ class ScannerTokens(tokens: Tokens, input: Input)(implicit dialect: Dialect) {
       @inline
       def strictNext: Token = getStrictAfterSafe(nextSafe)
 
-      def isLeadingInfixOperator: Boolean = dialect.allowInfixOperatorAfterNL && {
+      def isLeadingInfixOperator: Boolean = dialect.allowSignificantIndentation && {
         val text = token.text
         @tailrec
         def iter(idx: Int, nonEmpty: Boolean): Boolean = {


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/Scala213Suite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/Scala213Suite.scala
index a761059f3..54db44d15 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/Scala213Suite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/Scala213Suite.scala
@@ -1,8 +1,6 @@
 package scala.meta.tests
 package parsers
 
-import scala.meta.Dialect
-import scala.meta.dialects
 import scala.meta.dialects.Scala213
 
 class Scala213Suite extends ParseSuite {
@@ -80,18 +78,9 @@ class Scala213Suite extends ParseSuite {
     runAssert("try ()", """Term.Try(Lit.Unit(()), Nil, None)""")
   }
 
-  test("infix-at-line-start") {
-    runAssert(
-      """|val x = "hello"
-         |  ++ "world"
-         |""".stripMargin,
-      """Defn.Val(Nil, List(Pat.Var(Term.Name("x"))), None, Term.ApplyInfix(Lit.String("hello"), Term.Name("++"), Nil, List(Lit.String("world"))))"""
-    )(dialects.Scala213Source3)
-  }
-
-  private def runAssert(code: String, expected: String)(implicit d: Dialect): Unit = {
+  private def runAssert(code: String, expected: String): Unit = {
     assertNoDiff(
-      templStat(code)(d).structure,
+      templStat(code).structure,
       expected
     )
   }
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/TemplateSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/TemplateSuite.scala
index 3222a51d7..3bebb3fb5 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/TemplateSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/TemplateSuite.scala
@@ -337,4 +337,12 @@ class TemplateSuite extends ParseSuite {
     ) =
       templStat("object A { self: B => }")
   }
+
+  test("trait B extends A.type") {
+    val err = intercept[ParseException] {
+      templStat("trait B extends A.type")
+    }
+    assertNoDiff(err.shortMessage, "class type required but A.type found")
+  }
+
 }