From 016eb9f9fa4128300fb9401baf46370d8577f62c Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 8 Sep 2023 13:24:57 +0200 Subject: [PATCH 01/11] bugfix: highlight for enum type params [Cherry-picked e08c70e9332a4193f6267ce16741d9fee8b8105b] --- .../src/main/dotty/tools/pc/PcCollector.scala | 35 +++++++++++- .../highlight/DocumentHighlightSuite.scala | 57 ++++++++++++++++--- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala b/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala index 274b0704674d..fda62cbb25e4 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala @@ -111,6 +111,33 @@ abstract class PcCollector[T]( end adjust def symbolAlternatives(sym: Symbol) = + def member(parent: Symbol) = parent.info.member(sym.name).symbol + def primaryConstructorTypeParam(owner: Symbol) = + for + typeParams <- owner.primaryConstructor.paramSymss.headOption + param <- typeParams.find(_.name == sym.name) + if (param.isType) + yield param + def additionalForEnumTypeParam(enumClass: Symbol) = + if enumClass.is(Flags.Enum) then + val enumOwner = + if enumClass.is(Flags.Case) + then + Option.when(member(enumClass).is(Flags.Synthetic))( + enumClass.maybeOwner.companionClass + ) + else Some(enumClass) + enumOwner.toSet.flatMap { enumOwner => + val symsInEnumCases = enumOwner.children.toSet.flatMap(enumCase => + if member(enumCase).is(Flags.Synthetic) + then primaryConstructorTypeParam(enumCase) + else None + ) + val symsInEnumOwner = + primaryConstructorTypeParam(enumOwner).toSet + member(enumOwner) + symsInEnumCases ++ symsInEnumOwner + } + else Set.empty val all = if sym.is(Flags.ModuleClass) then Set(sym, sym.companionModule, sym.companionModule.companion) @@ -129,7 +156,11 @@ abstract class PcCollector[T]( ) ++ sym.allOverriddenSymbols.toSet // type used in primary constructor will not match the one used in the class else if sym.isTypeParam && sym.owner.isPrimaryConstructor then - Set(sym, sym.owner.owner.info.member(sym.name).symbol) + Set(sym, member(sym.maybeOwner.maybeOwner)) + ++ additionalForEnumTypeParam(sym.maybeOwner.maybeOwner) + else if sym.isTypeParam then + primaryConstructorTypeParam(sym.maybeOwner).toSet + ++ additionalForEnumTypeParam(sym.maybeOwner) + sym else Set(sym) all.filter(s => s != NoSymbol && !s.isError) end symbolAlternatives @@ -409,7 +440,7 @@ abstract class PcCollector[T]( * All select statements such as: * val a = hello.<> */ - case sel: Select + case sel: Select if sel.span.isCorrect && filter(sel) && !isForComprehensionMethod(sel) => occurrences + collect( diff --git a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala index 4bf04ed1d0af..6ed5d6c636e3 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala @@ -758,8 +758,8 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: | } |}""".stripMargin ) - - @Test def `for-comp-map` = + + @Test def `for-comp-map` = check( """|object Main { | val x = List(1).<>(_ + 1) @@ -770,7 +770,7 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: |""".stripMargin, ) - @Test def `for-comp-map1` = + @Test def `for-comp-map1` = check( """|object Main { | val x = List(1).<>(_ + 1) @@ -782,7 +782,7 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: |""".stripMargin, ) - @Test def `for-comp-foreach` = + @Test def `for-comp-foreach` = check( """|object Main { | val x = List(1).<>(_ => ()) @@ -793,7 +793,7 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: |""".stripMargin, ) - @Test def `for-comp-withFilter` = + @Test def `for-comp-withFilter` = check( """|object Main { | val x = List(1).<>(_ => true) @@ -805,7 +805,7 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: |""".stripMargin, ) - @Test def `for-comp-withFilter1` = + @Test def `for-comp-withFilter1` = check( """|object Main { | val x = List(1).withFilter(_ => true).<>(_ + 1) @@ -817,7 +817,7 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: |""".stripMargin, ) - @Test def `for-comp-flatMap1` = + @Test def `for-comp-flatMap1` = check( """|object Main { | val x = List(1).<>(_ => List(1)) @@ -830,7 +830,7 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: |""".stripMargin, ) - @Test def `for-comp-flatMap2` = + @Test def `for-comp-flatMap2` = check( """|object Main { | val x = List(1).withFilter(_ => true).<>(_ => List(1)) @@ -1102,3 +1102,44 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: |val alpha = MyOption.<>(1) |""".stripMargin, ) + + @Test def `type-params-in-enum` = + check( + """|enum MyOption[+<>]: + | case MySome(value: <>) + | case MyNone + |""".stripMargin, + ) + + @Test def `type-params-in-enum2` = + check( + """|enum MyOption[+<>]: + | case MySome(value: <>) + | case MyNone + |""".stripMargin, + ) + + @Test def `type-params-in-enum3` = + check( + """|enum MyOption[<>](v: <>): + | def get: <> = ??? + | case MySome[AA](value: AA) extends MyOption[Int](1) + |""".stripMargin, + ) + + @Test def `type-params-in-enum4` = + check( + """|enum MyOption[+<>]: + | def get: <> = ??? + | case MySome(value: <>) + | case MyNone + |""".stripMargin, + ) + + @Test def `type-params-in-enum5` = + check( + """|enum MyOption[AA]: + | def get: AA = ??? + | case MySome[<>](value: <>) extends MyOption[Int] + |""".stripMargin, + ) From 931cb921bff1796181151e5407a765462b65761a Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 25 Apr 2023 13:56:14 +0200 Subject: [PATCH 02/11] bugfix: in semanticdb make synthetic apply disambiguator consistent with Scala 2 implementation [Cherry-picked 7a44b59d8f733356d8050e8d122e933b7cac2813] --- .../semanticdb/SemanticSymbolBuilder.scala | 2 +- .../semanticdb/expect/CaseClass.expect.scala | 7 +++ tests/semanticdb/expect/CaseClass.scala | 7 +++ tests/semanticdb/metac.expect | 53 +++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 tests/semanticdb/expect/CaseClass.expect.scala create mode 100644 tests/semanticdb/expect/CaseClass.scala diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala index c7b0dfd437db..0b92ebddb02c 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala @@ -84,7 +84,7 @@ class SemanticSymbolBuilder: else decls0 end decls - val alts = decls.filter(_.isOneOf(Method | Mutable)).toList.reverse + val alts = decls.filter(_.isOneOf(Method | Mutable)).toList.reverse.partition(!_.is(Synthetic)).toList.flatten def find(filter: Symbol => Boolean) = alts match case notSym :: rest if !filter(notSym) => val idx = rest.indexWhere(filter).ensuring(_ >= 0) diff --git a/tests/semanticdb/expect/CaseClass.expect.scala b/tests/semanticdb/expect/CaseClass.expect.scala new file mode 100644 index 000000000000..d0c509855c70 --- /dev/null +++ b/tests/semanticdb/expect/CaseClass.expect.scala @@ -0,0 +1,7 @@ +package caseclass + +case class CaseClass/*<-caseclass::CaseClass#*/(int1/*<-caseclass::CaseClass#int1.*/: Int/*->scala::Int#*/, int2/*<-caseclass::CaseClass#int2.*/: Int/*->scala::Int#*/) + +object CaseClass/*<-caseclass::CaseClass.*/: + def apply/*<-caseclass::CaseClass.apply().*/(int/*<-caseclass::CaseClass.apply().(int)*/: Int/*->scala::Int#*/): CaseClass/*->caseclass::CaseClass#*/ = CaseClass/*->caseclass::CaseClass.*/(int/*->caseclass::CaseClass.apply().(int)*/, 0) + def apply/*<-caseclass::CaseClass.apply(+1).*/(): CaseClass/*->caseclass::CaseClass#*/ = CaseClass/*->caseclass::CaseClass.*/(0, 0) diff --git a/tests/semanticdb/expect/CaseClass.scala b/tests/semanticdb/expect/CaseClass.scala new file mode 100644 index 000000000000..576678112353 --- /dev/null +++ b/tests/semanticdb/expect/CaseClass.scala @@ -0,0 +1,7 @@ +package caseclass + +case class CaseClass(int1: Int, int2: Int) + +object CaseClass: + def apply(int: Int): CaseClass = CaseClass(int, 0) + def apply(): CaseClass = CaseClass(0, 0) diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index e456b192e015..ee63e6c3d0df 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -477,6 +477,59 @@ Occurrences: [4:4..4:7): bar <- angiven/AnonymousGiven$package.bar(). [4:14..4:17): Foo -> angiven/Foo# +expect/CaseClass.scala +---------------------- + +Summary: +Schema => SemanticDB v4 +Uri => CaseClass.scala +Text => empty +Language => Scala +Symbols => 22 entries +Occurrences => 16 entries + +Symbols: +caseclass/CaseClass# => case class CaseClass extends Object with Product with Serializable { self: CaseClass => +8 decls } +caseclass/CaseClass#_1(). => method _1 => Int +caseclass/CaseClass#_2(). => method _2 => Int +caseclass/CaseClass#``(). => primary ctor (val param int1: Int, val param int2: Int): CaseClass +caseclass/CaseClass#``().(int1) => val param int1: Int +caseclass/CaseClass#``().(int2) => val param int2: Int +caseclass/CaseClass#copy$default$1(). => method copy$default$1 => Int @uncheckedVariance +caseclass/CaseClass#copy$default$2(). => method copy$default$2 => Int @uncheckedVariance +caseclass/CaseClass#copy(). => method copy (param int1: Int, param int2: Int): CaseClass +caseclass/CaseClass#copy().(int1) => param int1: Int +caseclass/CaseClass#copy().(int2) => param int2: Int +caseclass/CaseClass#int1. => val method int1 Int +caseclass/CaseClass#int2. => val method int2 Int +caseclass/CaseClass. => final object CaseClass extends Object { self: CaseClass.type => +5 decls } +caseclass/CaseClass.apply(). => method apply (param int: Int): CaseClass +caseclass/CaseClass.apply().(int) => param int: Int +caseclass/CaseClass.apply(+1). => method apply (): CaseClass +caseclass/CaseClass.apply(+2). => method apply (param int1: Int, param int2: Int): CaseClass +caseclass/CaseClass.apply(+2).(int1) => param int1: Int +caseclass/CaseClass.apply(+2).(int2) => param int2: Int +caseclass/CaseClass.unapply(). => method unapply (param x$1: CaseClass): CaseClass +caseclass/CaseClass.unapply().(x$1) => param x$1: CaseClass + +Occurrences: +[0:8..0:17): caseclass <- caseclass/ +[2:11..2:20): CaseClass <- caseclass/CaseClass# +[2:21..2:25): int1 <- caseclass/CaseClass#int1. +[2:27..2:30): Int -> scala/Int# +[2:32..2:36): int2 <- caseclass/CaseClass#int2. +[2:38..2:41): Int -> scala/Int# +[4:7..4:16): CaseClass <- caseclass/CaseClass. +[5:6..5:11): apply <- caseclass/CaseClass.apply(). +[5:12..5:15): int <- caseclass/CaseClass.apply().(int) +[5:17..5:20): Int -> scala/Int# +[5:23..5:32): CaseClass -> caseclass/CaseClass# +[5:35..5:44): CaseClass -> caseclass/CaseClass. +[5:45..5:48): int -> caseclass/CaseClass.apply().(int) +[6:6..6:11): apply <- caseclass/CaseClass.apply(+1). +[6:15..6:24): CaseClass -> caseclass/CaseClass# +[6:27..6:36): CaseClass -> caseclass/CaseClass. + expect/Classes.scala -------------------- From ff12f9a4def5c8c6c1f83af668541ca2c9831dd0 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 14 Jul 2023 12:33:39 +0200 Subject: [PATCH 03/11] reduce some warnings in semanticdb tests [Cherry-picked bef7a4a81a2af9420fbbe3819ad028ef004c7aee] --- tests/semanticdb/expect/Enums.expect.scala | 2 +- tests/semanticdb/expect/Enums.scala | 2 +- tests/semanticdb/expect/Givens.expect.scala | 3 +- tests/semanticdb/expect/Givens.scala | 3 +- tests/semanticdb/metac.expect | 49 +++++++++++++-------- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/tests/semanticdb/expect/Enums.expect.scala b/tests/semanticdb/expect/Enums.expect.scala index 3e1dc9087db7..404dd14b07d2 100644 --- a/tests/semanticdb/expect/Enums.expect.scala +++ b/tests/semanticdb/expect/Enums.expect.scala @@ -46,7 +46,7 @@ object Enums/*<-_empty_::Enums.*/: enum <:_empty_::Enums.`<:<`.Refl#[C]*/ <:_empty_::Enums.`<:<`#*/ C/*->_empty_::Enums.`<:<`.Refl#[C]*/) - object <:_empty_::Enums.`<:<`.`given_<:<_T_T`().[T]*/ <:_empty_::Enums.`<:<`#*/ T/*->_empty_::Enums.`<:<`.`given_<:<_T_T`().[T]*/) = Refl/*->_empty_::Enums.`<:<`.Refl.*/() extension [A/*<-_empty_::Enums.unwrap().[A]*/, B/*<-_empty_::Enums.unwrap().[B]*/](opt/*<-_empty_::Enums.unwrap().(opt)*/: Option/*->scala::Option#*/[A/*->_empty_::Enums.unwrap().[A]*/]) def unwrap/*<-_empty_::Enums.unwrap().*/(using ev/*<-_empty_::Enums.unwrap().(ev)*/: A/*->_empty_::Enums.unwrap().[A]*/ <:_empty_::Enums.`<:<`#*/ Option/*->scala::Option#*/[B/*->_empty_::Enums.unwrap().[B]*/]): Option/*->scala::Option#*/[B/*->_empty_::Enums.unwrap().[B]*/] = ev/*->_empty_::Enums.unwrap().(ev)*/ match diff --git a/tests/semanticdb/expect/Enums.scala b/tests/semanticdb/expect/Enums.scala index be7e2d6ce5cb..7647c9cafeb7 100644 --- a/tests/semanticdb/expect/Enums.scala +++ b/tests/semanticdb/expect/Enums.scala @@ -46,7 +46,7 @@ object Enums: enum <:<[-A, B]: case Refl[C]() extends (C <:< C) - object <:< : + object `<:<`: given [T]: (T <:< T) = Refl() extension [A, B](opt: Option[A]) def unwrap(using ev: A <:< Option[B]): Option[B] = ev match diff --git a/tests/semanticdb/expect/Givens.expect.scala b/tests/semanticdb/expect/Givens.expect.scala index 5d7a9f5dc798..8ee82a419435 100644 --- a/tests/semanticdb/expect/Givens.expect.scala +++ b/tests/semanticdb/expect/Givens.expect.scala @@ -22,6 +22,7 @@ object Givens/*<-a::b::Givens.*/: def empty/*<-a::b::Givens.given_Monoid_String.empty().*/ = "" extension (x/*<-a::b::Givens.given_Monoid_String.combine().(x)*/: String/*->scala::Predef.String#*/) def combine/*<-a::b::Givens.given_Monoid_String.combine().*/(y/*<-a::b::Givens.given_Monoid_String.combine().(y)*/: String/*->scala::Predef.String#*/) = x/*->a::b::Givens.given_Monoid_String.combine().(x)*/ +/*->java::lang::String#`+`().*/ y/*->a::b::Givens.given_Monoid_String.combine().(y)*/ - inline given int2String/*<-a::b::Givens.int2String().*/: Conversion/*->scala::Conversion#*/[Int/*->scala::Int#*/, String/*->scala::Predef.String#*/] = _.toString/*->scala::Any#toString().*/ + inline given int2String/*<-a::b::Givens.int2String().*/: Conversion/*->scala::Conversion#*/[Int/*->scala::Int#*/, String/*->scala::Predef.String#*/] with + def apply/*<-a::b::Givens.int2String#apply().*/(x/*<-a::b::Givens.int2String#apply().(x)*/: Int/*->scala::Int#*/): String/*->scala::Predef.String#*/ = x/*->a::b::Givens.int2String#apply().(x)*/.toString/*->scala::Any#toString().*/ def foo/*<-a::b::Givens.foo().*/[A/*<-a::b::Givens.foo().[A]*/](using A/*<-a::b::Givens.foo().(A)*/: Monoid/*->a::b::Givens.Monoid#*/[A/*->a::b::Givens.foo().[A]*/]): A/*->a::b::Givens.foo().[A]*/ = A/*->a::b::Givens.foo().(A)*/.combine/*->a::b::Givens.Monoid#combine().*/(A/*->a::b::Givens.foo().(A)*/.empty/*->a::b::Givens.Monoid#empty().*/)(A/*->a::b::Givens.foo().(A)*/.empty/*->a::b::Givens.Monoid#empty().*/) diff --git a/tests/semanticdb/expect/Givens.scala b/tests/semanticdb/expect/Givens.scala index 819d70cfadca..f0a56e624d5d 100644 --- a/tests/semanticdb/expect/Givens.scala +++ b/tests/semanticdb/expect/Givens.scala @@ -22,6 +22,7 @@ object Givens: def empty = "" extension (x: String) def combine(y: String) = x + y - inline given int2String: Conversion[Int, String] = _.toString + inline given int2String: Conversion[Int, String] with + def apply(x: Int): String = x.toString def foo[A](using A: Monoid[A]): A = A.combine(A.empty)(A.empty) diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index ee63e6c3d0df..00727163fda4 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -486,7 +486,7 @@ Uri => CaseClass.scala Text => empty Language => Scala Symbols => 22 entries -Occurrences => 16 entries +Occurrences => 17 entries Symbols: caseclass/CaseClass# => case class CaseClass extends Object with Product with Serializable { self: CaseClass => +8 decls } @@ -515,6 +515,7 @@ caseclass/CaseClass.unapply().(x$1) => param x$1: CaseClass Occurrences: [0:8..0:17): caseclass <- caseclass/ [2:11..2:20): CaseClass <- caseclass/CaseClass# +[2:20..2:20): <- caseclass/CaseClass#``(). [2:21..2:25): int1 <- caseclass/CaseClass#int1. [2:27..2:30): Int -> scala/Int# [2:32..2:36): int2 <- caseclass/CaseClass#int2. @@ -1285,7 +1286,7 @@ Occurrences: [46:28..46:29): C -> _empty_/Enums.`<:<`.Refl#[C] [46:30..46:33): <:< -> _empty_/Enums.`<:<`# [46:34..46:35): C -> _empty_/Enums.`<:<`.Refl#[C] -[48:9..48:12): <:< <- _empty_/Enums.`<:<`. +[48:10..48:13): <:< <- _empty_/Enums.`<:<`. [49:11..49:12): T <- _empty_/Enums.`<:<`.`given_<:<_T_T`().[T] [49:16..49:17): T -> _empty_/Enums.`<:<`.`given_<:<_T_T`().[T] [49:18..49:21): <:< -> _empty_/Enums.`<:<`# @@ -1636,12 +1637,12 @@ Schema => SemanticDB v4 Uri => Givens.scala Text => empty Language => Scala -Symbols => 29 entries -Occurrences => 66 entries +Symbols => 33 entries +Occurrences => 72 entries Synthetics => 3 entries Symbols: -a/b/Givens. => final object Givens extends Object { self: Givens.type => +12 decls } +a/b/Givens. => final object Givens extends Object { self: Givens.type => +13 decls } a/b/Givens.Monoid# => trait Monoid [typeparam A ] extends Object { self: Monoid[A] => +4 decls } a/b/Givens.Monoid#[A] => typeparam A a/b/Givens.Monoid#``(). => primary ctor [typeparam A ](): Monoid[A] @@ -1659,7 +1660,11 @@ a/b/Givens.given_Monoid_String.combine().(y) => param y: String a/b/Givens.given_Monoid_String.empty(). => method empty => String <: a/b/Givens.Monoid#empty(). a/b/Givens.goodbye1. => val method goodbye1 String a/b/Givens.hello1. => val method hello1 String -a/b/Givens.int2String(). => final implicit given inline macro int2String => Conversion[Int, String] +a/b/Givens.int2String# => implicit given class int2String extends Conversion[Int, String] { self: int2String => +2 decls } +a/b/Givens.int2String#``(). => primary ctor (): int2String +a/b/Givens.int2String#apply(). => method apply (param x: Int): String <: scala/Conversion#apply()., scala/Function1#apply(). +a/b/Givens.int2String#apply().(x) => param x: Int +a/b/Givens.int2String(). => final implicit given inline macro int2String => int2String a/b/Givens.sayGoodbye(). => method sayGoodbye [typeparam B ](param any: B): String a/b/Givens.sayGoodbye().(any) => param any: B a/b/Givens.sayGoodbye().[B] => typeparam B @@ -1723,21 +1728,27 @@ Occurrences: [22:55..22:56): y -> a/b/Givens.given_Monoid_String.combine().(y) [24:15..24:25): int2String <- a/b/Givens.int2String(). [24:27..24:37): Conversion -> scala/Conversion# +[24:27..24:27): <- a/b/Givens.int2String#``(). [24:38..24:41): Int -> scala/Int# [24:43..24:49): String -> scala/Predef.String# -[24:55..24:63): toString -> scala/Any#toString(). -[26:6..26:9): foo <- a/b/Givens.foo(). -[26:10..26:11): A <- a/b/Givens.foo().[A] -[26:19..26:20): A <- a/b/Givens.foo().(A) -[26:22..26:28): Monoid -> a/b/Givens.Monoid# -[26:29..26:30): A -> a/b/Givens.foo().[A] -[26:34..26:35): A -> a/b/Givens.foo().[A] -[26:38..26:39): A -> a/b/Givens.foo().(A) -[26:40..26:47): combine -> a/b/Givens.Monoid#combine(). -[26:48..26:49): A -> a/b/Givens.foo().(A) -[26:50..26:55): empty -> a/b/Givens.Monoid#empty(). -[26:57..26:58): A -> a/b/Givens.foo().(A) -[26:59..26:64): empty -> a/b/Givens.Monoid#empty(). +[25:8..25:13): apply <- a/b/Givens.int2String#apply(). +[25:14..25:15): x <- a/b/Givens.int2String#apply().(x) +[25:17..25:20): Int -> scala/Int# +[25:23..25:29): String -> scala/Predef.String# +[25:32..25:33): x -> a/b/Givens.int2String#apply().(x) +[25:34..25:42): toString -> scala/Any#toString(). +[27:6..27:9): foo <- a/b/Givens.foo(). +[27:10..27:11): A <- a/b/Givens.foo().[A] +[27:19..27:20): A <- a/b/Givens.foo().(A) +[27:22..27:28): Monoid -> a/b/Givens.Monoid# +[27:29..27:30): A -> a/b/Givens.foo().[A] +[27:34..27:35): A -> a/b/Givens.foo().[A] +[27:38..27:39): A -> a/b/Givens.foo().(A) +[27:40..27:47): combine -> a/b/Givens.Monoid#combine(). +[27:48..27:49): A -> a/b/Givens.foo().(A) +[27:50..27:55): empty -> a/b/Givens.Monoid#empty(). +[27:57..27:58): A -> a/b/Givens.foo().(A) +[27:59..27:64): empty -> a/b/Givens.Monoid#empty(). Synthetics: [12:17..12:25):sayHello => *[Int] From 7823d4264d9ad3c33f195aa5962199c61e39c7c8 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 15 Sep 2023 10:48:14 +0200 Subject: [PATCH 04/11] Add missing span to extension method select [Cherry-picked fe4a6855efb1e59e050f3ebf00cfd91a32675eec] --- .../dotty/tools/dotc/inlines/Inliner.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 2 +- .../tools/dotc/typer/ErrorReporting.scala | 3 +- .../dotty/tools/dotc/typer/Implicits.scala | 12 +++--- .../tools/dotc/typer/ImportSuggestions.scala | 2 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 39 ++++++++++--------- .../src/dotty/tools/dotc/typer/Typer.scala | 10 ++--- .../tests/definition/PcDefinitionSuite.scala | 10 +++++ .../highlight/DocumentHighlightSuite.scala | 31 +++++++++++++++ .../tools/pc/tests/hover/HoverTypeSuite.scala | 12 ++++++ .../semanticdb/expect/Extension.expect.scala | 9 +++++ tests/semanticdb/expect/Extension.scala | 9 +++++ tests/semanticdb/metac.expect | 26 +++++++++++-- 13 files changed, 130 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 29d6a5365978..66a83c66b5f8 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -771,7 +771,7 @@ class Inliner(val call: tpd.Tree)(using Context): override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { val locked = ctx.typerState.ownedVars - val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) + val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) val reducedProjection = reducer.reduceProjection(resNoReduce) if reducedProjection.isType then diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 19af6b02c25d..09daa429a08f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -959,7 +959,7 @@ trait Applications extends Compatibility { val resultType = if !originalResultType.isRef(defn.ObjectClass) then originalResultType else AvoidWildcardsMap()(proto.resultType.deepenProtoTrans) match - case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _) => resTp + case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _, _) => resTp case resTp if isFullyDefined(resTp, ForceDegree.all) => resTp case _ => defn.ObjectType val methType = MethodType(proto.typedArgs().map(_.tpe.widen), resultType) diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 339d1f2f7bc6..2da934c6013a 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -8,6 +8,7 @@ import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._ import Implicits._, Flags._, Constants.Constant import Trees._ import NameOps._ +import util.Spans.NoSpan import util.SrcPos import config.Feature import reporting._ @@ -266,7 +267,7 @@ object ErrorReporting { else val add = suggestImports( ViewProto(qualType.widen, - SelectionProto(tree.name, WildcardType, NoViewsAllowed, privateOK = false))) + SelectionProto(tree.name, WildcardType, NoViewsAllowed, privateOK = false, NoSpan))) if add.isEmpty then "" else ", but could be made available as an extension method." ++ add end selectErrorAddendum diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 29a35ccbdac0..3872d0dd79d0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -75,7 +75,7 @@ object Implicits: * method with the selecting name? False otherwise. */ def hasExtMethod(tp: Type, expected: Type)(using Context) = expected match - case selProto @ SelectionProto(selName: TermName, _, _, _) => + case selProto @ SelectionProto(selName: TermName, _, _, _, _) => tp.memberBasedOnFlags(selName, required = ExtensionMethod).exists case _ => false @@ -437,7 +437,7 @@ object Implicits: def clarify(tp: Type)(using Context): Type = tp final protected def qualify(using Context): String = expectedType match { - case SelectionProto(name, mproto, _, _) if !argument.isEmpty => + case SelectionProto(name, mproto, _, _, _) if !argument.isEmpty => i"provide an extension method `$name` on ${argument.tpe}" case NoType => if (argument.isEmpty) i"match expected type" @@ -842,8 +842,8 @@ trait Implicits: NoMatchingImplicitsFailure else { def adjust(to: Type) = to.stripTypeVar.widenExpr match { - case SelectionProto(name, memberProto, compat, true) => - SelectionProto(name, memberProto, compat, privateOK = false) + case SelectionProto(name, memberProto, compat, true, nameSpan) => + SelectionProto(name, memberProto, compat, privateOK = false, nameSpan) case tp => tp } @@ -1137,10 +1137,10 @@ trait Implicits: pt, locked) } pt match - case selProto @ SelectionProto(selName: TermName, mbrType, _, _) => + case selProto @ SelectionProto(selName: TermName, mbrType, _, _, nameSpan) => def tryExtension(using Context) = - extMethodApply(untpd.Select(untpdGenerated, selName), argument, mbrType) + extMethodApply(untpd.Select(untpdGenerated, selName).withSpan(nameSpan), argument, mbrType) def tryConversionForSelection(using Context) = val converted = tryConversion diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 70addd442100..a21a94aab271 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -237,7 +237,7 @@ trait ImportSuggestions: // don't suggest things that are imported by default def extensionImports = pt match - case ViewProto(argType, SelectionProto(name: TermName, _, _, _)) => + case ViewProto(argType, SelectionProto(name: TermName, _, _, _, _)) => roots.flatMap(extensionMethod(_, name, argType)) case _ => Nil diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index bde279c582e6..31cca0301d7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -19,6 +19,7 @@ import util.SourceFile import TypeComparer.necessarySubType import scala.annotation.internal.sharable +import dotty.tools.dotc.util.Spans.{NoSpan, Span} object ProtoTypes { @@ -165,7 +166,7 @@ object ProtoTypes { * * [ ].name: proto */ - abstract case class SelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean) + abstract case class SelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean, nameSpan: Span) extends CachedProxyType with ProtoType with ValueTypeOrProto { /** Is the set of members of this type unknown, in the sense that we @@ -230,9 +231,9 @@ object ProtoTypes { def underlying(using Context): Type = WildcardType - def derivedSelectionProto(name: Name, memberProto: Type, compat: Compatibility)(using Context): SelectionProto = - if ((name eq this.name) && (memberProto eq this.memberProto) && (compat eq this.compat)) this - else SelectionProto(name, memberProto, compat, privateOK) + def derivedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, nameSpan: Span)(using Context): SelectionProto = + if ((name eq this.name) && (memberProto eq this.memberProto) && (compat eq this.compat) && (nameSpan == this.nameSpan)) this + else SelectionProto(name, memberProto, compat, privateOK, nameSpan) override def isErroneous(using Context): Boolean = memberProto.isErroneous @@ -240,14 +241,14 @@ object ProtoTypes { override def unusableForInference(using Context): Boolean = memberProto.unusableForInference - def map(tm: TypeMap)(using Context): SelectionProto = derivedSelectionProto(name, tm(memberProto), compat) + def map(tm: TypeMap)(using Context): SelectionProto = derivedSelectionProto(name, tm(memberProto), compat, nameSpan) def fold[T](x: T, ta: TypeAccumulator[T])(using Context): T = ta(x, memberProto) override def deepenProto(using Context): SelectionProto = - derivedSelectionProto(name, memberProto.deepenProto, compat) + derivedSelectionProto(name, memberProto.deepenProto, compat, nameSpan) override def deepenProtoTrans(using Context): SelectionProto = - derivedSelectionProto(name, memberProto.deepenProtoTrans, compat) + derivedSelectionProto(name, memberProto.deepenProtoTrans, compat, nameSpan) override def computeHash(bs: Hashable.Binders): Int = { val delta = (if (compat eq NoViewsAllowed) 1 else 0) | (if (privateOK) 2 else 0) @@ -268,12 +269,12 @@ object ProtoTypes { } } - class CachedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean) - extends SelectionProto(name, memberProto, compat, privateOK) + class CachedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean, nameSpan: Span) + extends SelectionProto(name, memberProto, compat, privateOK, nameSpan) object SelectionProto { - def apply(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean)(using Context): SelectionProto = { - val selproto = new CachedSelectionProto(name, memberProto, compat, privateOK) + def apply(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean, nameSpan: Span)(using Context): SelectionProto = { + val selproto = new CachedSelectionProto(name, memberProto, compat, privateOK, nameSpan) if (compat eq NoViewsAllowed) unique(selproto) else selproto } } @@ -281,11 +282,11 @@ object ProtoTypes { /** Create a selection proto-type, but only one level deep; * treat constructors specially */ - def shallowSelectionProto(name: Name, tp: Type, typer: Typer)(using Context): TermType = + def shallowSelectionProto(name: Name, tp: Type, typer: Typer, nameSpan: Span)(using Context): TermType = if (name.isConstructorName) WildcardType else tp match - case tp: UnapplyFunProto => new UnapplySelectionProto(name) - case tp => SelectionProto(name, IgnoredProto(tp), typer, privateOK = true) + case tp: UnapplyFunProto => new UnapplySelectionProto(name, nameSpan) + case tp => SelectionProto(name, IgnoredProto(tp), typer, privateOK = true, nameSpan) /** A prototype for expressions [] that are in some unspecified selection operation * @@ -295,12 +296,12 @@ object ProtoTypes { * operation is further selection. In this case, the expression need not be a value. * @see checkValue */ - @sharable object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true) + @sharable object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true, NoSpan) - @sharable object SingletonTypeProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true) + @sharable object SingletonTypeProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true, NoSpan) /** A prototype for selections in pattern constructors */ - class UnapplySelectionProto(name: Name) extends SelectionProto(name, WildcardType, NoViewsAllowed, true) + class UnapplySelectionProto(name: Name, nameSpan: Span) extends SelectionProto(name, WildcardType, NoViewsAllowed, true, nameSpan) trait ApplyingProto extends ProtoType // common trait of ViewProto and FunProto trait FunOrPolyProto extends ProtoType: // common trait of PolyProto and FunProto @@ -599,7 +600,7 @@ object ProtoTypes { def isMatchedBy(tp: Type, keepConstraint: Boolean)(using Context): Boolean = ctx.typer.isApplicableType(tp, argType :: Nil, resultType) || { resType match { - case selProto @ SelectionProto(selName: TermName, mbrType, _, _) => + case selProto @ SelectionProto(selName: TermName, mbrType, _, _, _) => ctx.typer.hasExtensionMethodNamed(tp, selName, argType, mbrType) //.reporting(i"has ext $tp $name $argType $mbrType: $result") case _ => @@ -921,7 +922,7 @@ object ProtoTypes { } approxOr case tp: SelectionProto => - tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto, theMap, seen, internal), NoViewsAllowed) + tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto, theMap, seen, internal), NoViewsAllowed, tp.nameSpan) case tp: ViewProto => tp.derivedViewProto( wildApprox(tp.argType, theMap, seen, internal), diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 34940d2f5277..c18a369207a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -752,7 +752,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer record("typedSelect") def typeSelectOnTerm(using Context): Tree = - val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) + val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) typedSelect(tree, pt, qual).withSpan(tree.span).computeNullable() def javaSelectOnType(qual: Tree)(using Context) = @@ -782,7 +782,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tryAlternatively(typeSelectOnTerm)(fallBack) if (tree.qualifier.isType) { - val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) + val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) assignType(cpy.Select(tree)(qual1, tree.name), qual1) } else if (ctx.isJava && tree.name.isTypeName) @@ -3442,7 +3442,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer then Some(adapt(tree, pt, locked)) else - val selProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false) + val selProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false, tree.nameSpan) if selProto.isMatchedBy(qual.tpe) || tree.hasAttachment(InsertedImplicitOnQualifier) then None else @@ -3467,7 +3467,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer (tree: untpd.Select, pt: Type, mbrProto: Type, qual: Tree, locked: TypeVars, compat: Compatibility, inSelect: Boolean) (using Context): Tree = - def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK = inSelect) + def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK = inSelect, tree.nameSpan) def tryExtension(using Context): Tree = val altImports = new mutable.ListBuffer[TermRef]() @@ -3897,7 +3897,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer * function prototype `(...)R`. Otherwise `pt`. */ def ptWithoutRedundantApply: Type = pt.revealIgnored match - case SelectionProto(nme.apply, mpt, _, _) => + case SelectionProto(nme.apply, mpt, _, _, _) => mpt.revealIgnored match case fpt: FunProto => fpt case _ => pt diff --git a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala index 4869cdf0fa3b..2b49d2db3f08 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala @@ -401,3 +401,13 @@ class PcDefinitionSuite extends BasePcDefinitionSuite: | |""".stripMargin ) + + @Test def `implicit-extension` = + check( + """|class MyIntOut(val value: Int) + |object MyIntOut: + | extension (i: MyIntOut) def <> = i.value % 2 == 1 + | + |val a = MyIntOut(1).un@@even + |""".stripMargin, + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala index 6ed5d6c636e3..0ed40c6c537f 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala @@ -1143,3 +1143,34 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: | case MySome[<>](value: <>) extends MyOption[Int] |""".stripMargin, ) + + @Test def `implicit-extension` = + check( + """|class MyIntOut(val value: Int) + |object MyIntOut: + | extension (i: MyIntOut) def <> = i.value % 2 == 1 + | + |val a = MyIntOut(1) + |val m = a.<> + |""".stripMargin, + ) + + @Test def `implicit-extension-2` = + check( + """|class MyIntOut(val value: Int) + |object MyIntOut: + | extension (i: MyIntOut) def <>(u: Int) = i.value % 2 == 1 + | + |val a = MyIntOut(1).<>(3) + |""".stripMargin, + ) + + @Test def `implicit-extension-infix` = + check( + """|class MyIntOut(val value: Int) + |object MyIntOut: + | extension (i: MyIntOut) def <<++>>(u: Int) = i.value + u + | + |val a = MyIntOut(1) <<+@@+>> 3 + |""".stripMargin, + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala index 12c3a7be584f..984066fb7803 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala @@ -355,3 +355,15 @@ class HoverTypeSuite extends BaseHoverSuite: """|val ddd: Int |""".stripMargin.hover, ) + + @Test def `infix-extension` = + check( + """|class MyIntOut(val value: Int) + |object MyIntOut: + | extension (i: MyIntOut) def uneven = i.value % 2 == 1 + | + |val a = MyIntOut(1).un@@even + |""".stripMargin, + """|extension (i: MyIntOut) def uneven: Boolean + |""".stripMargin.hover, + ) diff --git a/tests/semanticdb/expect/Extension.expect.scala b/tests/semanticdb/expect/Extension.expect.scala index b40e965d4885..f6f76b17b698 100644 --- a/tests/semanticdb/expect/Extension.expect.scala +++ b/tests/semanticdb/expect/Extension.expect.scala @@ -16,3 +16,12 @@ extension (s/*<-ext::Extension$package.readInto().(s)*/: String/*->scala::Predef trait Functor/*<-ext::Functor#*/[F/*<-ext::Functor#[F]*/[_]]: extension [T/*<-ext::Functor#map().[T]*/](t/*<-ext::Functor#map().(t)*/: F/*->ext::Functor#[F]*/[T/*->ext::Functor#map().[T]*/]) def map/*<-ext::Functor#map().*/[U/*<-ext::Functor#map().[U]*/](f/*<-ext::Functor#map().(f)*/: T/*->ext::Functor#map().[T]*/ => U/*->ext::Functor#map().[U]*/): F/*->ext::Functor#[F]*/[U/*->ext::Functor#map().[U]*/] + +opaque type Deck/*<-ext::Extension$package.Deck#*/ = Long/*->scala::Long#*/ +object Deck/*<-ext::Extension$package.Deck.*/: + extension (data/*<-ext::Extension$package.Deck.fooSize().(data)*/: Deck/*->ext::Extension$package.Deck#*/) + def fooSize/*<-ext::Extension$package.Deck.fooSize().*/: Int/*->scala::Int#*/ = ???/*->scala::Predef.`???`().*/ + +object DeckUsage/*<-ext::DeckUsage.*/: + val deck/*<-ext::DeckUsage.deck.*/: Deck/*->ext::Extension$package.Deck#*/ = ???/*->scala::Predef.`???`().*/ + deck/*->ext::DeckUsage.deck.*/.fooSize/*->ext::Extension$package.Deck.fooSize().*/ diff --git a/tests/semanticdb/expect/Extension.scala b/tests/semanticdb/expect/Extension.scala index c204b1ff7fcc..76a012e4b758 100644 --- a/tests/semanticdb/expect/Extension.scala +++ b/tests/semanticdb/expect/Extension.scala @@ -16,3 +16,12 @@ extension (s: String) trait Functor[F[_]]: extension [T](t: F[T]) def map[U](f: T => U): F[U] + +opaque type Deck = Long +object Deck: + extension (data: Deck) + def fooSize: Int = ??? + +object DeckUsage: + val deck: Deck = ??? + deck.fooSize diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 00727163fda4..d8aa1e39abef 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -1450,12 +1450,18 @@ Schema => SemanticDB v4 Uri => Extension.scala Text => empty Language => Scala -Symbols => 26 entries -Occurrences => 52 entries +Symbols => 32 entries +Occurrences => 66 entries Synthetics => 1 entries Symbols: -ext/Extension$package. => final package object ext extends Object { self: ext.type => +6 decls } +ext/DeckUsage. => final object DeckUsage extends Object { self: DeckUsage.type => +2 decls } +ext/DeckUsage.deck. => val method deck Deck +ext/Extension$package. => final package object ext extends Object { self: ext.type { opaque type Deck } => +9 decls } +ext/Extension$package.Deck# => opaque type Deck +ext/Extension$package.Deck. => final object Deck extends Object { self: Deck.type => +2 decls } +ext/Extension$package.Deck.fooSize(). => method fooSize (param data: Deck): Int +ext/Extension$package.Deck.fooSize().(data) => param data: Deck ext/Extension$package.`#*#`(). => method #*# (param s: String)(param i: Int): Tuple2[String, Int] ext/Extension$package.`#*#`().(i) => param i: Int ext/Extension$package.`#*#`().(s) => param s: String @@ -1535,6 +1541,20 @@ Occurrences: [17:44..17:45): U -> ext/Functor#map().[U] [17:48..17:49): F -> ext/Functor#[F] [17:50..17:51): U -> ext/Functor#map().[U] +[19:12..19:16): Deck <- ext/Extension$package.Deck# +[19:19..19:23): Long -> scala/Long# +[20:7..20:11): Deck <- ext/Extension$package.Deck. +[21:13..21:17): data <- ext/Extension$package.Deck.fooSize().(data) +[21:19..21:23): Deck -> ext/Extension$package.Deck# +[22:8..22:15): fooSize <- ext/Extension$package.Deck.fooSize(). +[22:17..22:20): Int -> scala/Int# +[22:23..22:26): ??? -> scala/Predef.`???`(). +[24:7..24:16): DeckUsage <- ext/DeckUsage. +[25:6..25:10): deck <- ext/DeckUsage.deck. +[25:12..25:16): Deck -> ext/Extension$package.Deck# +[25:19..25:22): ??? -> scala/Predef.`???`(). +[26:2..26:6): deck -> ext/DeckUsage.deck. +[26:7..26:14): fooSize -> ext/Extension$package.Deck.fooSize(). Synthetics: [14:46..14:61):summon[Read[T]] => *(x$2) From 95b6ff2dac4d8771cba74295c34ddfea4cc4f085 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Tue, 19 Sep 2023 15:32:37 +0200 Subject: [PATCH 05/11] Avoid flattening `List` of `List`s [Cherry-picked c39d260042332803259563110603521ed6381db6] --- .../tools/dotc/transform/CheckUnused.scala | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index f40993314257..a3dbbfb202e7 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -561,11 +561,19 @@ object CheckUnused: else Nil val warnings = - List(sortedImp, sortedLocalDefs, sortedExplicitParams, sortedImplicitParams, - sortedPrivateDefs, sortedPatVars, unsetLocalDefs, unsetPrivateDefs).flatten.sortBy { s => - val pos = s.pos.sourcePos - (pos.line, pos.column) - } + val unsorted = + sortedImp ::: + sortedLocalDefs ::: + sortedExplicitParams ::: + sortedImplicitParams ::: + sortedPrivateDefs ::: + sortedPatVars ::: + unsetLocalDefs ::: + unsetPrivateDefs + unsorted.sortBy { s => + val pos = s.pos.sourcePos + (pos.line, pos.column) + } UnusedResult(warnings.toSet) end getUnused //============================ HELPERS ==================================== From 473656b472c91053c66b7271a0298e35794eef3c Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Tue, 19 Sep 2023 15:44:14 +0200 Subject: [PATCH 06/11] Remove redundant `.toList` call on a `List` Co-authored-by: Nicolas Stucki [Cherry-picked 0c04c2e5206e8e5ade2cc8c31efb394803e62e28] --- compiler/src/dotty/tools/MainGenericRunner.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/MainGenericRunner.scala b/compiler/src/dotty/tools/MainGenericRunner.scala index 6f4366a00b77..0a5a1b191a44 100644 --- a/compiler/src/dotty/tools/MainGenericRunner.scala +++ b/compiler/src/dotty/tools/MainGenericRunner.scala @@ -148,7 +148,7 @@ object MainGenericRunner { case (o @ javaOption(striped)) :: tail => processArgs(tail, settings.withJavaArgs(striped).withScalaArgs(o)) case (o @ scalaOption(_*)) :: tail => - val remainingArgs = (CommandLineParser.expandArg(o) ++ tail).toList + val remainingArgs = CommandLineParser.expandArg(o) ++ tail processArgs(remainingArgs, settings) case (o @ colorOption(_*)) :: tail => processArgs(tail, settings.withScalaArgs(o)) From b5e560b50c391c88e9ecabd27cc9e162d75fa7e1 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Tue, 19 Sep 2023 15:48:18 +0200 Subject: [PATCH 07/11] Avoid creating a List to initialize fields [Cherry-picked c3377d4900cba307d291fc56e39187431fc9aa7a] --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2f60d5f4d3cb..c76b6423548c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -2130,8 +2130,10 @@ class Definitions { this.initCtx = ctx if (!isInitialized) { // force initialization of every symbol that is synthesized or hijacked by the compiler - val forced = - syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses() :+ JavaEnumClass + syntheticCoreClasses + syntheticCoreMethods + JavaEnumClass + // end force initialization isInitialized = true } addSyntheticSymbolsComments From bf4e4ebd510180429f6bd429c0fe6477af6ae094 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Wed, 20 Sep 2023 12:01:46 +0200 Subject: [PATCH 08/11] Add back `ScalaValueClasses()` initialization The call to `apply` perform side effects that need to be executed [Cherry-picked 240e95ae148189c46ce990d0d71db968f7cffe63] --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index c76b6423548c..953f91459eda 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -2132,6 +2132,7 @@ class Definitions { // force initialization of every symbol that is synthesized or hijacked by the compiler syntheticCoreClasses syntheticCoreMethods + ScalaValueClasses() JavaEnumClass // end force initialization isInitialized = true From 53794f1fa237e3f5d631de04e59a7592ed51ab8f Mon Sep 17 00:00:00 2001 From: Jakub Ciesluk <323892@uwr.edu.pl> Date: Thu, 21 Sep 2023 10:50:44 +0200 Subject: [PATCH 09/11] bugfix: Incorrect semanticdb span on Selectable Semanticdb range on selectDynamic in `foo.bar` previously contained `.bar` instead of `bar` [Cherry-picked 5df5855ac5099363583e43751e16cd1808880c78] --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 +- .../dotc/semanticdb/ExtractSemanticDB.scala | 11 +-- tests/semanticdb/expect/Givens.expect.scala | 6 +- .../expect/ImplicitConversion.expect.scala | 6 +- .../expect/StructuralTypes.expect.scala | 22 +++++ tests/semanticdb/expect/StructuralTypes.scala | 22 +++++ tests/semanticdb/metac.expect | 87 ++++++++++++++++--- 7 files changed, 132 insertions(+), 24 deletions(-) create mode 100644 tests/semanticdb/expect/StructuralTypes.expect.scala create mode 100644 tests/semanticdb/expect/StructuralTypes.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 8d19dfdf6743..c202f7ccb523 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -455,7 +455,7 @@ object Trees { val point = span.point if name.toTermName == nme.ERROR then Span(point) - else if qualifier.span.start > span.start then // right associative + else if qualifier.span.start > span.point then // right associative val realName = name.stripModuleClassSuffix.lastPart Span(span.start, span.start + realName.length, point) else diff --git a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala index 5cff105cbaa4..e364bf15ca13 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala @@ -392,15 +392,12 @@ class ExtractSemanticDB extends Phase: }).toMap end findGetters - private def selectSpan(tree: Select) = + private def selectSpan(tree: Select)(using Context) = val end = tree.span.end val limit = tree.qualifier.span.end - val start = - if limit < end then - val len = tree.name.toString.length - if tree.source.content()(end - 1) == '`' then end - len - 2 else end - len - else limit - Span(start max limit, end) + if limit < end then + tree.nameSpan + else Span(limit, end) extension (span: Span) private def hasLength: Boolean = span.exists && !span.isZeroExtent diff --git a/tests/semanticdb/expect/Givens.expect.scala b/tests/semanticdb/expect/Givens.expect.scala index 8ee82a419435..8cd1ee287096 100644 --- a/tests/semanticdb/expect/Givens.expect.scala +++ b/tests/semanticdb/expect/Givens.expect.scala @@ -4,11 +4,11 @@ package b object Givens/*<-a::b::Givens.*/: extension [A/*<-a::b::Givens.sayHello().[A]*/](any/*<-a::b::Givens.sayHello().(any)*/: A/*->a::b::Givens.sayHello().[A]*/) - def sayHello/*<-a::b::Givens.sayHello().*/ = s"Hello, I am $any/*->a::b::Givens.sayHello().(any)*/"/*->scala::StringContext#s().*/ + def sayHello/*<-a::b::Givens.sayHello().*/ = s/*->scala::StringContext#s().*/"Hello, I am $any/*->a::b::Givens.sayHello().(any)*/" extension [B/*<-a::b::Givens.sayGoodbye().[B]*//*<-a::b::Givens.saySoLong().[B]*/](any/*<-a::b::Givens.sayGoodbye().(any)*//*<-a::b::Givens.saySoLong().(any)*/: B/*->a::b::Givens.sayGoodbye().[B]*//*->a::b::Givens.saySoLong().[B]*/) - def sayGoodbye/*<-a::b::Givens.sayGoodbye().*/ = s"Goodbye, from $any/*->a::b::Givens.sayGoodbye().(any)*/"/*->scala::StringContext#s().*/ - def saySoLong/*<-a::b::Givens.saySoLong().*/ = s"So Long, from $any/*->a::b::Givens.saySoLong().(any)*/"/*->scala::StringContext#s().*/ + def sayGoodbye/*<-a::b::Givens.sayGoodbye().*/ = s/*->scala::StringContext#s().*/"Goodbye, from $any/*->a::b::Givens.sayGoodbye().(any)*/" + def saySoLong/*<-a::b::Givens.saySoLong().*/ = s/*->scala::StringContext#s().*/"So Long, from $any/*->a::b::Givens.saySoLong().(any)*/" val hello1/*<-a::b::Givens.hello1.*/ = 1.sayHello/*->a::b::Givens.sayHello().*/ val goodbye1/*<-a::b::Givens.goodbye1.*/ = 1.sayGoodbye/*->a::b::Givens.sayGoodbye().*/ diff --git a/tests/semanticdb/expect/ImplicitConversion.expect.scala b/tests/semanticdb/expect/ImplicitConversion.expect.scala index 635ffb4d94c9..7c1708ee7617 100644 --- a/tests/semanticdb/expect/ImplicitConversion.expect.scala +++ b/tests/semanticdb/expect/ImplicitConversion.expect.scala @@ -21,10 +21,10 @@ class ImplicitConversion/*<-example::ImplicitConversion#*/ { val x/*<-example::ImplicitConversion#x.*/: Int/*->scala::Int#*/ = message/*->example::ImplicitConversion#message.*/ // interpolators - s"Hello $message/*->example::ImplicitConversion#message.*/ $number/*->example::ImplicitConversion#number.*/"/*->scala::StringContext#s().*/ - s"""Hello + s/*->scala::StringContext#s().*/"Hello $message/*->example::ImplicitConversion#message.*/ $number/*->example::ImplicitConversion#number.*/" + s/*->scala::StringContext#s().*/"""Hello |$message/*->example::ImplicitConversion#message.*/ - |$number/*->example::ImplicitConversion#number.*/"""/*->scala::StringContext#s().*/.stripMargin/*->scala::collection::StringOps#stripMargin(+1).*/ + |$number/*->example::ImplicitConversion#number.*/""".stripMargin/*->scala::collection::StringOps#stripMargin(+1).*/ val a/*<-example::ImplicitConversion#a.*/: Int/*->scala::Int#*/ = char/*->example::ImplicitConversion#char.*/ val b/*<-example::ImplicitConversion#b.*/: Long/*->scala::Long#*/ = char/*->example::ImplicitConversion#char.*/ diff --git a/tests/semanticdb/expect/StructuralTypes.expect.scala b/tests/semanticdb/expect/StructuralTypes.expect.scala new file mode 100644 index 000000000000..96c7181d6f10 --- /dev/null +++ b/tests/semanticdb/expect/StructuralTypes.expect.scala @@ -0,0 +1,22 @@ +package example + +import reflect.Selectable/*->scala::reflect::Selectable.*/.reflectiveSelectable/*->scala::reflect::Selectable.reflectiveSelectable().*/ + +object StructuralTypes/*<-example::StructuralTypes.*/: + type User/*<-example::StructuralTypes.User#*/ = { + def name/*<-local0*/: String/*->scala::Predef.String#*/ + def age/*<-local1*/: Int/*->scala::Int#*/ + def foo/*<-local3*/(x/*<-local2*/: Int/*->scala::Int#*/): Int/*->scala::Int#*/ + } + + val user/*<-example::StructuralTypes.user.*/ = null.asInstanceOf/*->scala::Any#asInstanceOf().*/[User/*->example::StructuralTypes.User#*/] + user/*->example::StructuralTypes.user.*/.name/*->scala::reflect::Selectable#selectDynamic().*/ + user/*->example::StructuralTypes.user.*/.age/*->scala::reflect::Selectable#selectDynamic().*/ + val fooBar/*<-example::StructuralTypes.fooBar.*/ = user/*->example::StructuralTypes.user.*/ foo/*->scala::reflect::Selectable#applyDynamic().*/ 123 + + val V/*<-example::StructuralTypes.V.*/: Object/*->java::lang::Object#*/ { + def scalameta/*<-local4*/: String/*->scala::Predef.String#*/ + } = /*<-local6*/new: + def scalameta/*<-local5*/ = "4.0" + V/*->example::StructuralTypes.V.*/.scalameta/*->scala::reflect::Selectable#selectDynamic().*/ +end StructuralTypes/*->example::StructuralTypes.*/ \ No newline at end of file diff --git a/tests/semanticdb/expect/StructuralTypes.scala b/tests/semanticdb/expect/StructuralTypes.scala new file mode 100644 index 000000000000..5d10dbe67224 --- /dev/null +++ b/tests/semanticdb/expect/StructuralTypes.scala @@ -0,0 +1,22 @@ +package example + +import reflect.Selectable.reflectiveSelectable + +object StructuralTypes: + type User = { + def name: String + def age: Int + def foo(x: Int): Int + } + + val user = null.asInstanceOf[User] + user.name + user.age + val fooBar = user foo 123 + + val V: Object { + def scalameta: String + } = new: + def scalameta = "4.0" + V.scalameta +end StructuralTypes \ No newline at end of file diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index d8aa1e39abef..d2220e2af17a 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -193,21 +193,21 @@ Occurrences: [27:6..27:9): s1x <- advanced/Test.s1x. [27:12..27:13): s -> advanced/Test.s. [27:14..27:16): s1 -> advanced/Structural#s1(). -[27:16..27:18): .x -> scala/reflect/Selectable#selectDynamic(). +[27:17..27:18): x -> scala/reflect/Selectable#selectDynamic(). [28:6..28:8): s2 <- advanced/Test.s2. [28:11..28:12): s -> advanced/Test.s. [28:13..28:15): s2 -> advanced/Structural#s2(). [29:6..29:9): s2x <- advanced/Test.s2x. [29:12..29:13): s -> advanced/Test.s. [29:14..29:16): s2 -> advanced/Structural#s2(). -[29:16..29:18): .x -> scala/reflect/Selectable#selectDynamic(). +[29:17..29:18): x -> scala/reflect/Selectable#selectDynamic(). [30:6..30:8): s3 <- advanced/Test.s3. [30:11..30:12): s -> advanced/Test.s. [30:13..30:15): s3 -> advanced/Structural#s3(). [31:6..31:9): s3x <- advanced/Test.s3x. [31:12..31:13): s -> advanced/Test.s. [31:14..31:16): s3 -> advanced/Structural#s3(). -[31:16..31:18): .m -> scala/reflect/Selectable#applyDynamic(). +[31:17..31:18): m -> scala/reflect/Selectable#applyDynamic(). [31:19..31:22): ??? -> scala/Predef.`???`(). [33:6..33:7): e <- advanced/Test.e. [33:14..33:23): Wildcards -> advanced/Wildcards# @@ -242,7 +242,7 @@ Occurrences: [47:11..47:14): foo -> advanced/Test.foo. [47:15..47:16): A -> local17 [47:19..47:22): foo -> advanced/Test.foo. -[47:22..47:24): .a -> scala/reflect/Selectable#selectDynamic(). +[47:23..47:24): a -> scala/reflect/Selectable#selectDynamic(). [52:6..52:13): HKClass <- advanced/HKClass# [52:13..52:13): <- advanced/HKClass#``(). [52:14..52:15): F <- advanced/HKClass#[F] @@ -1704,8 +1704,8 @@ Occurrences: [5:16..5:19): any <- a/b/Givens.sayHello().(any) [5:21..5:22): A -> a/b/Givens.sayHello().[A] [6:8..6:16): sayHello <- a/b/Givens.sayHello(). +[6:19..6:20): s -> scala/StringContext#s(). [6:34..6:37): any -> a/b/Givens.sayHello().(any) -[6:37..6:38): " -> scala/StringContext#s(). [8:13..8:14): B <- a/b/Givens.sayGoodbye().[B] [8:13..8:14): B <- a/b/Givens.saySoLong().[B] [8:16..8:19): any <- a/b/Givens.sayGoodbye().(any) @@ -1713,11 +1713,11 @@ Occurrences: [8:21..8:22): B -> a/b/Givens.sayGoodbye().[B] [8:21..8:22): B -> a/b/Givens.saySoLong().[B] [9:8..9:18): sayGoodbye <- a/b/Givens.sayGoodbye(). +[9:21..9:22): s -> scala/StringContext#s(). [9:38..9:41): any -> a/b/Givens.sayGoodbye().(any) -[9:41..9:42): " -> scala/StringContext#s(). [10:8..10:17): saySoLong <- a/b/Givens.saySoLong(). +[10:20..10:21): s -> scala/StringContext#s(). [10:37..10:40): any -> a/b/Givens.saySoLong().(any) -[10:40..10:41): " -> scala/StringContext#s(). [12:6..12:12): hello1 <- a/b/Givens.hello1. [12:17..12:25): sayHello -> a/b/Givens.sayHello(). [13:6..13:14): goodbye1 <- a/b/Givens.goodbye1. @@ -1836,12 +1836,12 @@ Occurrences: [20:6..20:7): x <- example/ImplicitConversion#x. [20:9..20:12): Int -> scala/Int# [20:15..20:22): message -> example/ImplicitConversion#message. +[23:2..23:3): s -> scala/StringContext#s(). [23:11..23:18): message -> example/ImplicitConversion#message. [23:20..23:26): number -> example/ImplicitConversion#number. -[23:26..23:27): " -> scala/StringContext#s(). +[24:2..24:3): s -> scala/StringContext#s(). [25:7..25:14): message -> example/ImplicitConversion#message. [26:7..26:13): number -> example/ImplicitConversion#number. -[26:15..26:16): " -> scala/StringContext#s(). [26:17..26:28): stripMargin -> scala/collection/StringOps#stripMargin(+1). [28:6..28:7): a <- example/ImplicitConversion#a. [28:9..28:12): Int -> scala/Int# @@ -3346,6 +3346,73 @@ Occurrences: [13:17..13:17): <- selfs/C6#``(). [13:27..13:28): B -> selfs/B# +expect/StructuralTypes.scala +---------------------------- + +Summary: +Schema => SemanticDB v4 +Uri => StructuralTypes.scala +Text => empty +Language => Scala +Symbols => 12 entries +Occurrences => 33 entries +Synthetics => 4 entries + +Symbols: +example/StructuralTypes. => final object StructuralTypes extends Object { self: StructuralTypes.type => +5 decls } +example/StructuralTypes.User# => type User = Object { abstract method foo (param x: Int): Int; abstract method age => Int; abstract method name => String } +example/StructuralTypes.V. => val method V Object { abstract method scalameta => String } +example/StructuralTypes.fooBar. => val method fooBar Int +example/StructuralTypes.user. => val method user User +local0 => abstract method name => String +local1 => abstract method age => Int +local2 => param x: Int +local3 => abstract method foo (param x: Int): Int +local4 => abstract method scalameta => String +local5 => method scalameta => String +local6 => final class $anon extends Object { self: $anon => +2 decls } + +Occurrences: +[0:8..0:15): example <- example/ +[2:7..2:14): reflect -> scala/reflect/ +[2:15..2:25): Selectable -> scala/reflect/Selectable. +[2:26..2:46): reflectiveSelectable -> scala/reflect/Selectable.reflectiveSelectable(). +[4:7..4:22): StructuralTypes <- example/StructuralTypes. +[5:7..5:11): User <- example/StructuralTypes.User# +[6:8..6:12): name <- local0 +[6:14..6:20): String -> scala/Predef.String# +[7:8..7:11): age <- local1 +[7:13..7:16): Int -> scala/Int# +[8:8..8:11): foo <- local3 +[8:12..8:13): x <- local2 +[8:15..8:18): Int -> scala/Int# +[8:21..8:24): Int -> scala/Int# +[11:6..11:10): user <- example/StructuralTypes.user. +[11:18..11:30): asInstanceOf -> scala/Any#asInstanceOf(). +[11:31..11:35): User -> example/StructuralTypes.User# +[12:2..12:6): user -> example/StructuralTypes.user. +[12:7..12:11): name -> scala/reflect/Selectable#selectDynamic(). +[13:2..13:6): user -> example/StructuralTypes.user. +[13:7..13:10): age -> scala/reflect/Selectable#selectDynamic(). +[14:6..14:12): fooBar <- example/StructuralTypes.fooBar. +[14:15..14:19): user -> example/StructuralTypes.user. +[14:20..14:23): foo -> scala/reflect/Selectable#applyDynamic(). +[16:6..16:7): V <- example/StructuralTypes.V. +[16:9..16:15): Object -> java/lang/Object# +[17:8..17:17): scalameta <- local4 +[17:19..17:25): String -> scala/Predef.String# +[18:6..18:6): <- local6 +[19:8..19:17): scalameta <- local5 +[20:2..20:3): V -> example/StructuralTypes.V. +[20:4..20:13): scalameta -> scala/reflect/Selectable#selectDynamic(). +[21:4..21:19): StructuralTypes -> example/StructuralTypes. + +Synthetics: +[12:2..12:6):user => reflectiveSelectable(*) +[13:2..13:6):user => reflectiveSelectable(*) +[14:15..14:19):user => reflectiveSelectable(*) +[20:2..20:3):V => reflectiveSelectable(*) + expect/Synthetic.scala ---------------------- @@ -3944,7 +4011,7 @@ Occurrences: [48:18..48:19): v -> example/ValUsages.v. [48:20..48:23): yim -> example/Vals#yim(). [49:2..49:3): v -> example/ValUsages.v. -[49:3..49:18): .explicitSetter -> example/Vals#`explicitSetter_=`(). +[49:4..49:18): explicitSetter -> example/Vals#`explicitSetter_=`(). expect/Vararg.scala ------------------- From c711f63949c3f84e1c54070449a182100bc5d0d9 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 21 Sep 2023 16:48:13 +0200 Subject: [PATCH 10/11] remove implementation details from enum spec [Cherry-picked a7d1e3332dbee3588fa1da1f2538d0be8d82b7fe] --- docs/_spec/05-classes-and-objects.md | 157 +++++++++++---------------- 1 file changed, 62 insertions(+), 95 deletions(-) diff --git a/docs/_spec/05-classes-and-objects.md b/docs/_spec/05-classes-and-objects.md index d777ebfe73e8..cc8d97704a50 100644 --- a/docs/_spec/05-classes-and-objects.md +++ b/docs/_spec/05-classes-and-objects.md @@ -904,7 +904,7 @@ Very much like a concrete class definition, an object definition may still conta ```ebnf TmplDef ::= ‘enum’ EnumDef -EnumDef ::= id ClassConstr [‘extends’ [ConstrApps]] EnumBody +EnumDef ::= id ClassConstr [‘extends’ ConstrApps] EnumBody EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ EnumStat ::= TemplateStat | {Annotation [nl]} {Modifier} EnumCase @@ -925,18 +925,15 @@ First, some terminology and notational conventions: - We use `<...>` for syntactic constructs that in some circumstances might be empty. For instance, `` represents one or more parameter lists `(´\mathit{ps}_1\,´)...(´\mathit{ps}_n´)` or nothing at all. - Enum classes fall into two categories: - - _parameterized_ enum classes have at least one of the following: - - a type parameter section, denoted as `[´\mathit{tps}\,´]`; - - one or more (possibly empty) parameter sections, denoted as `(´\mathit{ps}_1\,´)...(´\mathit{ps}_n´)`. - - _unparameterized_ enum classes have no type parameter sections and no parameter sections. + - _parameterized_ enum classes have at least one or more (possibly empty) term parameter clauses, denoted as `(´\mathit{ps}_1\,´)...(´\mathit{ps}_n´)`. + - _unparameterized_ enum classes have no term parameter clauses, but may optionally have a type parameter clause, denoted as `[´\mathit{tps}\,´]`. - Enum cases fall into three categories: - - - _Class cases_ are those cases that are parameterized, either with a type parameter section `[´\mathit{tps}\,´]` or with one or more (possibly empty) parameter sections `(´\mathit{ps}_1\,´)...(´\mathit{ps}_n´)`. - - _Simple cases_ are cases of an unparameterized enum that have neither parameters nor an extends clause or body. + - _Class enum cases_ are those cases that possibly have a type parameter clause `[´\mathit{tps}\,´]`, and necessarily have one or more (possibly empty) parameter clauses `(´\mathit{ps}_1\,´)...(´\mathit{ps}_n´)`. + - _Simple enum cases_ are those cases that have no parameter clauses and no extends clause. That is, they consist of a name only. - - _Value cases_ are all cases that do not have a parameter section but that do have a (possibly generated) `extends` clause and/or a body. + - _Value enum cases_ are those cases that have no parameter clauses but that do have a (possibly generated) `extends` clause. -- Simple cases and value cases are collectively called _singleton cases_. +- Simple enum cases and value enum cases are collectively called _singleton enum cases_. ###### Example @@ -970,13 +967,11 @@ enum Option[+T]: ### Lowering of Enum Definitions ###### Summary -An enum class is represented as a `sealed` class that extends the `scala.reflect.Enum` trait. +An enum class is represented as a `sealed abstract` class that extends the `scala.reflect.Enum` trait. Enum cases are represented as follows: -- a class case is mapped to a `case class`, -- a singleton case is mapped to a `val` definition, where - - Simple cases all share a single implementation class. - - Value cases will each be implemented by a unique class. +- a class enum case is mapped to a `case class` member of enum class' companion object, +- a singleton enum case is mapped to a `val` member of the enum class' companion object, implemented by a local class definition which may be shared between cases. ###### Precise rules The `scala.reflect.Enum` trait defines a single public method, `ordinal`: @@ -989,106 +984,119 @@ transparent trait Enum extends Any, Product, Serializable: ``` There are nine desugaring rules. Rule (1) desugars enum definitions. -Rules (2) and (3) desugar simple cases. -Rules (4) to (6) define `extends` clauses for cases that are missing them. -Rules (7) to (9) define how such cases with `extends` clauses map into `case class`es or `val`s. +Rule (2) desugars cases of comma-separated names to simple enum cases. +Rules (3) to (7) desugar inferrable details of enum cases. +Rules (8) and (9) define how fully-desugared enum cases map into `case class`es or `val`s. +Explicit `extends` clauses must be provided in the following cases, where rules (2) to (6) do not apply: +- any enum case of a parameterized enum, +- any singleton enum case of an unparameterized enum with non-variant type parameters, +- any class enum case of an enum with type parameters, where the case also has type parameters. 1. An `enum` definition ```scala - enum ´E´ ... { } + enum ´E´ { } ``` expands to a `sealed abstract` class that extends the `scala.reflect.Enum` trait and an associated companion object that contains the defined cases, expanded according to rules (2 - 8). The enum class starts with a compiler-generated import that imports the names `` of all cases so that they can be used without prefix in the class. ```scala - sealed abstract class ´E´ ... extends with scala.reflect.Enum { - import ´E´.{ } - + sealed abstract class ´E´ + extends with scala.reflect.Enum { + import ´E´.{ } + } object ´E´ { } ``` -2. A singleton case consisting of a comma-separated list of enum names +2. A simple enum case consisting of a comma-separated list of names ```scala case ´C_1´, ..., ´C_n´ ``` - expands to + expands to the following simple enum cases ```scala case ´C_1´; ...; case ´C_n´ ``` Any modifiers or annotations on the original case extend to all expanded cases. - This result is then further rewritten by either (3 or 4). +

This result is then further rewritten by either (3 or 4).

-3. A singleton case without an extends clause +3. A simple enum case `´C´` of an unparameterized enum `´E´` without type parameters ```scala case ´C´ ``` - of an unparameterized enum `´E´` expands to the following simple enum case in `´E´`'s companion object: + expands to the following value enum case: ```scala - val ´C´ = $new(n, "C") + case ´C´ extends ´E´ ``` - Here, `$new` is a private method that creates an instance of ´E´ (see below). + This result is then further rewritten with rule (8). -4. A singleton case without an extends clause +4. A simple enum case `´C´` of an unparameterized enum `´E´[´\mathit{tps}´]` with type parameters ```scala case ´C´ ``` - of an enum `´E´` with type parameters + where `´\mathit{tps}´` are of the following form ```scala ´\mathit{v}_1´ ´T_1´ >: ´L_1´ <: ´U_1´ , ... , ´\mathit{v}_n´ ´T_n´ >: ´L_n´ <: ´U_n´ (n > 0) ``` - where each of the variances `´\mathit{v}_i´` is either `'+'` or `'-'`, expands to the following value enum case: + and where each of the variances `´\mathit{v}_i´` is either `'+'` or `'-'`, expands to the following value enum case: ```scala case ´C´ extends ´E´[´B_1´, ..., ´B_n´] ``` where `´B_i´` is `´L_i´` if `´\mathit{v}_i´ = '+'` and `´U_i´` if `´\mathit{v}_i´ = '-'`. - This result is then further rewritten with rule (8). - **NOTE:** It is not permitted for enums with non-variant type parameters to have singleton cases without an extends clause. +

This result is then further rewritten with rule (8).

-5. A class case without an extends clause +5. A class enum case with type parameters, but without an extends clause ```scala - case ´C´ + case ´C´[´\mathit{tps}´](´\mathit{ps}_1\,´)...(´\mathit{ps}_n´) ``` - of an enum `´E´` that does not take type parameters expands to + of an unparameterized enum `´E´` without type parameters expands to ```scala - case ´C´ extends ´E´ + case ´C´[´\mathit{tps}´](´\mathit{ps}_1\,´)...(´\mathit{ps}_n´) extends ´E´ ``` This result is then further rewritten with rule (9). -6. If `´E´` is an enum with type parameters `´\mathit{tps}´`, a class case with neither type parameters nor an extends clause +6. A class enum case without type parameters or an extends clause ```scala - case ´C´ + case ´C´(´\mathit{ps}_1\,´)...(´\mathit{ps}_n´) ``` - expands to + of an unparameterized enum `´E´[´\mathit{tps}´]` with type parameters expands to ```scala - case ´C´[´\mathit{tps}´] extends ´E´[´\mathit{tps}´] + case ´C´(´\mathit{ps}_1\,´)...(´\mathit{ps}_n´) extends ´E´[´\mathit{tps}´] ``` - This result is then further rewritten with rule (9). - For class cases that have type parameters themselves, an extends clause needs to be given explicitly. - + This result is then further rewritten with rule (7). -7. If `´E´` is an enum with type parameters `´\mathit{tps}´`, a class case without type parameters but with an extends clause +7. A class enum case without type parameters, but has an extends clause ```scala - case ´C´ extends + case ´C´(´\mathit{ps}_1\,´)...(´\mathit{ps}_n´) extends ``` - expands to + of an enum `´E´[´\mathit{tps}´]` with type parameters expands to ```scala - case ´C´[´\mathit{tps}´] extends + case ´C´[´\mathit{tps}´](´\mathit{ps}_1\,´)...(´\mathit{ps}_n´) extends ``` - provided at least one of the parameters `´\mathit{tps}´` is mentioned in a parameter type in `` or in a type argument in ``. + provided at least one of the parameters `´\mathit{tps}´` is mentioned in a parameter type in `(´\mathit{ps}_1\,´)...(´\mathit{ps}_n´)` or in a type argument in ``. +

+ This result is then further rewritten with rule (9). -8. A value case +8. A singleton enum case ```scala case ´C´ extends ``` expands to the following `val` definition in `´E´`'s companion object: ```scala - val ´C´ = new { ; def ordinal = ´\mathit{n}´ } + val ´C´ = $factory(_$ordinal = ´\mathit{n}´, $name = "C") ``` where `´\mathit{n}´` is the ordinal number of the case in the companion object, starting from 0. + `$factory` is a placeholder that expands its arguments into an expression that produces something equivalent to + a new instance of the following (possibly shared) anonymous class: + ```scala + new { + def ordinal: Int = _$ordinal + override def toString: String = $name + } + ``` The anonymous class also implements the abstract `Product` methods that it inherits from `Enum`. +

**NOTE:** It is an error if a value case refers to a type parameter of `´E´` in a type argument within ``. -9. A class case +9. A class enum case ```scala case ´C´ extends ``` @@ -1099,6 +1107,7 @@ Rules (7) to (9) define how such cases with `extends` clauses map into `case cla } ``` where `´\mathit{n}´` is the ordinal number of the case in the companion object, starting from 0. +

**NOTE:** It is an error if a class case refers to a type parameter of `´E´` in a parameter type in `` or `` or in a type argument of ``, unless that parameter is already a type parameter of the case, i.e. the parameter name is defined in ``. ###### Superclass of an enum case @@ -1131,34 +1140,6 @@ private def $new(_$ordinal: Int, $name: String) = override def toString = $name ``` - -###### Example - -Consider the more complex enumeration `Color`, consisting of value enum cases: -```scala -enum Color(val rgb: Int): - case Red extends Color(0xFF0000) - case Green extends Color(0x00FF00) - case Blue extends Color(0x0000FF) -``` - -The three value cases will expand as follows in the companion of `Color`: - -```scala -val Red = new Color(0xFF0000): - def ordinal: Int = 0 - override def productPrefix: String = "Red" - override def toString: String = "Red" -val Green = new Color(0x00FF00): - def ordinal: Int = 1 - override def productPrefix: String = "Green" - override def toString: String = "Green" -val Blue = new Color(0x0000FF): - def ordinal: Int = 2 - override def productPrefix: String = "Blue" - override def toString: String = "Blue" -``` - ### Widening of enum cases post-construction The compiler-generated `apply` and `copy` methods of an class enum case ```scala @@ -1176,20 +1157,6 @@ An enum `´E´` (possibly generic) that defines one or more singleton cases, and It returns the singleton case value whose identifier is `name`. - A method `values` which returns an `Array[´E'´]` of all singleton case values defined by `E`, in the order of their definitions. -### Factory method for simple enum cases - -If an enum `´E´` contains at least one simple case, its companion object will define in addition: - - - A private method `$new` which defines a new simple case value with given ordinal number and name. - This method can be thought as being defined as follows. - - ```scala - private def $new(_$ordinal: Int, $name: String): ´E´ with runtime.EnumValue - ``` - - `$new` returns a new instance of an anonymous class which implements the abstract `Product` methods that it inherits from `Enum`. - - if `´E´` inherits from `java.lang.Enum` the anonymous class does not override the `ordinal` or `toString` methods, as these are final in `java.lang.Enum`. - Additionally `productPrefix` will delegate to `this.name`. - ### Translation of Java-compatible enums A Java-compatible enum is an enum that extends `java.lang.Enum`. From 34093beecd3136c760bd1bf6c92eb851ab247491 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 22 Sep 2023 11:56:50 +0200 Subject: [PATCH 11/11] address review comments [Cherry-picked 57a0f174762e6009e46c77ba309d33b5301d499d] --- docs/_spec/05-classes-and-objects.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/_spec/05-classes-and-objects.md b/docs/_spec/05-classes-and-objects.md index cc8d97704a50..e1d4ace3d81f 100644 --- a/docs/_spec/05-classes-and-objects.md +++ b/docs/_spec/05-classes-and-objects.md @@ -970,8 +970,8 @@ enum Option[+T]: An enum class is represented as a `sealed abstract` class that extends the `scala.reflect.Enum` trait. Enum cases are represented as follows: -- a class enum case is mapped to a `case class` member of enum class' companion object, -- a singleton enum case is mapped to a `val` member of the enum class' companion object, implemented by a local class definition which may be shared between cases. +- a class enum case is mapped to a `case class` member of the enum class' companion object, +- a singleton enum case is mapped to a `val` member of the enum class' companion object, implemented by a local class definition. Whether that local class is shared with other singleton cases, and which ones, is left as an implementation detail. ###### Precise rules The `scala.reflect.Enum` trait defines a single public method, `ordinal`: @@ -994,7 +994,7 @@ Explicit `extends` clauses must be provided in the following cases, where rules 1. An `enum` definition ```scala - enum ´E´ { } + enum ´E´ extends { } ``` expands to a `sealed abstract` class that extends the `scala.reflect.Enum` trait and an associated companion object that contains the defined cases, expanded according to rules (2 - 8). The enum class starts with a compiler-generated import that imports the names `` of all cases so that they can be used without prefix in the class.