Skip to content

Commit 2486191

Browse files
authored
Add missing span to extension method select (#18557)
For: ```Demo.scala class MyIntOut(val value: Int) object MyIntOut: extension (i: MyIntOut) def uneven = i.value % 2 == 1 val a = MyIntOut(1).uneven ``` `uneven` call in the typed tree has span `<126..126>`, where it should be `<138..144>`. connected to: scalameta/metals#5630 I'm open to suggestions on how to do it nicer.
2 parents a37dac6 + fe4a685 commit 2486191

File tree

13 files changed

+130
-37
lines changed

13 files changed

+130
-37
lines changed

Diff for: compiler/src/dotty/tools/dotc/inlines/Inliner.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,7 @@ class Inliner(val call: tpd.Tree)(using Context):
771771

772772
override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = {
773773
val locked = ctx.typerState.ownedVars
774-
val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this))
774+
val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
775775
val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt)
776776
val reducedProjection = reducer.reduceProjection(resNoReduce)
777777
if reducedProjection.isType then

Diff for: compiler/src/dotty/tools/dotc/typer/Applications.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,7 @@ trait Applications extends Compatibility {
976976
val resultType =
977977
if !originalResultType.isRef(defn.ObjectClass) then originalResultType
978978
else AvoidWildcardsMap()(proto.resultType.deepenProtoTrans) match
979-
case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _) => resTp
979+
case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _, _) => resTp
980980
case resTp if isFullyDefined(resTp, ForceDegree.all) => resTp
981981
case _ => defn.ObjectType
982982
val methType = MethodType(proto.typedArgs().map(_.tpe.widen), resultType)

Diff for: compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._
88
import Implicits._, Flags._, Constants.Constant
99
import Trees._
1010
import NameOps._
11+
import util.Spans.NoSpan
1112
import util.SrcPos
1213
import config.Feature
1314
import reporting._
@@ -275,7 +276,7 @@ object ErrorReporting {
275276
else
276277
val add = suggestImports(
277278
ViewProto(qualType.widen,
278-
SelectionProto(tree.name, WildcardType, NoViewsAllowed, privateOK = false)))
279+
SelectionProto(tree.name, WildcardType, NoViewsAllowed, privateOK = false, NoSpan)))
279280
if add.isEmpty then ""
280281
else ", but could be made available as an extension method." ++ add
281282
end selectErrorAddendum

Diff for: compiler/src/dotty/tools/dotc/typer/Implicits.scala

+6-6
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ object Implicits:
7676
* method with the selecting name? False otherwise.
7777
*/
7878
def hasExtMethod(tp: Type, expected: Type)(using Context) = expected match
79-
case selProto @ SelectionProto(selName: TermName, _, _, _) =>
79+
case selProto @ SelectionProto(selName: TermName, _, _, _, _) =>
8080
tp.memberBasedOnFlags(selName, required = ExtensionMethod).exists
8181
case _ =>
8282
false
@@ -454,7 +454,7 @@ object Implicits:
454454
def clarify(tp: Type)(using Context): Type = tp
455455

456456
final protected def qualify(using Context): String = expectedType match {
457-
case SelectionProto(name, mproto, _, _) if !argument.isEmpty =>
457+
case SelectionProto(name, mproto, _, _, _) if !argument.isEmpty =>
458458
i"provide an extension method `$name` on ${argument.tpe}"
459459
case NoType =>
460460
if (argument.isEmpty) i"match expected type"
@@ -866,8 +866,8 @@ trait Implicits:
866866
NoMatchingImplicitsFailure
867867
else {
868868
def adjust(to: Type) = to.stripTypeVar.widenExpr match {
869-
case SelectionProto(name, memberProto, compat, true) =>
870-
SelectionProto(name, memberProto, compat, privateOK = false)
869+
case SelectionProto(name, memberProto, compat, true, nameSpan) =>
870+
SelectionProto(name, memberProto, compat, privateOK = false, nameSpan)
871871
case tp => tp
872872
}
873873

@@ -1161,10 +1161,10 @@ trait Implicits:
11611161
pt, locked)
11621162
}
11631163
pt match
1164-
case selProto @ SelectionProto(selName: TermName, mbrType, _, _) =>
1164+
case selProto @ SelectionProto(selName: TermName, mbrType, _, _, nameSpan) =>
11651165

11661166
def tryExtension(using Context) =
1167-
extMethodApply(untpd.Select(untpdGenerated, selName), argument, mbrType)
1167+
extMethodApply(untpd.Select(untpdGenerated, selName).withSpan(nameSpan), argument, mbrType)
11681168

11691169
def tryConversionForSelection(using Context) =
11701170
val converted = tryConversion

Diff for: compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ trait ImportSuggestions:
237237
// don't suggest things that are imported by default
238238

239239
def extensionImports = pt match
240-
case ViewProto(argType, SelectionProto(name: TermName, _, _, _)) =>
240+
case ViewProto(argType, SelectionProto(name: TermName, _, _, _, _)) =>
241241
roots.flatMap(extensionMethod(_, name, argType))
242242
case _ =>
243243
Nil

Diff for: compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

+20-19
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import dotty.tools.dotc.core.Flags.Transparent
2121
import dotty.tools.dotc.config.{ Feature, SourceVersion }
2222

2323
import scala.annotation.internal.sharable
24+
import dotty.tools.dotc.util.Spans.{NoSpan, Span}
2425

2526
object ProtoTypes {
2627

@@ -180,7 +181,7 @@ object ProtoTypes {
180181
*
181182
* [ ].name: proto
182183
*/
183-
abstract case class SelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean)
184+
abstract case class SelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean, nameSpan: Span)
184185
extends CachedProxyType with ProtoType with ValueTypeOrProto {
185186

186187
/** Is the set of members of this type unknown, in the sense that we
@@ -243,24 +244,24 @@ object ProtoTypes {
243244

244245
def underlying(using Context): Type = WildcardType
245246

246-
def derivedSelectionProto(name: Name, memberProto: Type, compat: Compatibility)(using Context): SelectionProto =
247-
if ((name eq this.name) && (memberProto eq this.memberProto) && (compat eq this.compat)) this
248-
else SelectionProto(name, memberProto, compat, privateOK)
247+
def derivedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, nameSpan: Span)(using Context): SelectionProto =
248+
if ((name eq this.name) && (memberProto eq this.memberProto) && (compat eq this.compat) && (nameSpan == this.nameSpan)) this
249+
else SelectionProto(name, memberProto, compat, privateOK, nameSpan)
249250

250251
override def isErroneous(using Context): Boolean =
251252
memberProto.isErroneous
252253

253254
override def unusableForInference(using Context): Boolean =
254255
memberProto.unusableForInference
255256

256-
def map(tm: TypeMap)(using Context): SelectionProto = derivedSelectionProto(name, tm(memberProto), compat)
257+
def map(tm: TypeMap)(using Context): SelectionProto = derivedSelectionProto(name, tm(memberProto), compat, nameSpan)
257258
def fold[T](x: T, ta: TypeAccumulator[T])(using Context): T = ta(x, memberProto)
258259

259260
override def deepenProto(using Context): SelectionProto =
260-
derivedSelectionProto(name, memberProto.deepenProto, compat)
261+
derivedSelectionProto(name, memberProto.deepenProto, compat, nameSpan)
261262

262263
override def deepenProtoTrans(using Context): SelectionProto =
263-
derivedSelectionProto(name, memberProto.deepenProtoTrans, compat)
264+
derivedSelectionProto(name, memberProto.deepenProtoTrans, compat, nameSpan)
264265

265266
override def computeHash(bs: Hashable.Binders): Int = {
266267
val delta = (if (compat eq NoViewsAllowed) 1 else 0) | (if (privateOK) 2 else 0)
@@ -281,24 +282,24 @@ object ProtoTypes {
281282
}
282283
}
283284

284-
class CachedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean)
285-
extends SelectionProto(name, memberProto, compat, privateOK)
285+
class CachedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean, nameSpan: Span)
286+
extends SelectionProto(name, memberProto, compat, privateOK, nameSpan)
286287

287288
object SelectionProto {
288-
def apply(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean)(using Context): SelectionProto = {
289-
val selproto = new CachedSelectionProto(name, memberProto, compat, privateOK)
289+
def apply(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean, nameSpan: Span)(using Context): SelectionProto = {
290+
val selproto = new CachedSelectionProto(name, memberProto, compat, privateOK, nameSpan)
290291
if (compat eq NoViewsAllowed) unique(selproto) else selproto
291292
}
292293
}
293294

294295
/** Create a selection proto-type, but only one level deep;
295296
* treat constructors specially
296297
*/
297-
def shallowSelectionProto(name: Name, tp: Type, typer: Typer)(using Context): TermType =
298+
def shallowSelectionProto(name: Name, tp: Type, typer: Typer, nameSpan: Span)(using Context): TermType =
298299
if (name.isConstructorName) WildcardType
299300
else tp match
300-
case tp: UnapplyFunProto => new UnapplySelectionProto(name)
301-
case tp => SelectionProto(name, IgnoredProto(tp), typer, privateOK = true)
301+
case tp: UnapplyFunProto => new UnapplySelectionProto(name, nameSpan)
302+
case tp => SelectionProto(name, IgnoredProto(tp), typer, privateOK = true, nameSpan)
302303

303304
/** A prototype for expressions [] that are in some unspecified selection operation
304305
*
@@ -308,12 +309,12 @@ object ProtoTypes {
308309
* operation is further selection. In this case, the expression need not be a value.
309310
* @see checkValue
310311
*/
311-
@sharable object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true)
312+
@sharable object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true, NoSpan)
312313

313-
@sharable object SingletonTypeProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true)
314+
@sharable object SingletonTypeProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true, NoSpan)
314315

315316
/** A prototype for selections in pattern constructors */
316-
class UnapplySelectionProto(name: Name) extends SelectionProto(name, WildcardType, NoViewsAllowed, true)
317+
class UnapplySelectionProto(name: Name, nameSpan: Span) extends SelectionProto(name, WildcardType, NoViewsAllowed, true, nameSpan)
317318

318319
trait ApplyingProto extends ProtoType // common trait of ViewProto and FunProto
319320
trait FunOrPolyProto extends ProtoType: // common trait of PolyProto and FunProto
@@ -612,7 +613,7 @@ object ProtoTypes {
612613
def isMatchedBy(tp: Type, keepConstraint: Boolean)(using Context): Boolean =
613614
ctx.typer.isApplicableType(tp, argType :: Nil, resultType) || {
614615
resType match {
615-
case selProto @ SelectionProto(selName: TermName, mbrType, _, _) =>
616+
case selProto @ SelectionProto(selName: TermName, mbrType, _, _, _) =>
616617
ctx.typer.hasExtensionMethodNamed(tp, selName, argType, mbrType)
617618
//.reporting(i"has ext $tp $name $argType $mbrType: $result")
618619
case _ =>
@@ -934,7 +935,7 @@ object ProtoTypes {
934935
}
935936
approxOr
936937
case tp: SelectionProto =>
937-
tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto, theMap, seen, internal), NoViewsAllowed)
938+
tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto, theMap, seen, internal), NoViewsAllowed, tp.nameSpan)
938939
case tp: ViewProto =>
939940
tp.derivedViewProto(
940941
wildApprox(tp.argType, theMap, seen, internal),

Diff for: compiler/src/dotty/tools/dotc/typer/Typer.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
760760
record("typedSelect")
761761

762762
def typeSelectOnTerm(using Context): Tree =
763-
val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this))
763+
val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
764764
typedSelect(tree, pt, qual).withSpan(tree.span).computeNullable()
765765

766766
def javaSelectOnType(qual: Tree)(using Context) =
@@ -790,7 +790,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
790790
tryAlternatively(typeSelectOnTerm)(fallBack)
791791

792792
if (tree.qualifier.isType) {
793-
val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this))
793+
val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
794794
assignType(cpy.Select(tree)(qual1, tree.name), qual1)
795795
}
796796
else if (ctx.isJava && tree.name.isTypeName)
@@ -3513,7 +3513,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
35133513
then
35143514
Some(adapt(tree, pt, locked))
35153515
else
3516-
val selProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false)
3516+
val selProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false, tree.nameSpan)
35173517
if selProto.isMatchedBy(qual.tpe) || tree.hasAttachment(InsertedImplicitOnQualifier) then
35183518
None
35193519
else
@@ -3538,7 +3538,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
35383538
(tree: untpd.Select, pt: Type, mbrProto: Type, qual: Tree, locked: TypeVars, compat: Compatibility, inSelect: Boolean)
35393539
(using Context): Tree =
35403540

3541-
def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK = inSelect)
3541+
def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK = inSelect, tree.nameSpan)
35423542

35433543
def tryExtension(using Context): Tree =
35443544
val altImports = new mutable.ListBuffer[TermRef]()
@@ -3968,7 +3968,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
39683968
* function prototype `(...)R`. Otherwise `pt`.
39693969
*/
39703970
def ptWithoutRedundantApply: Type = pt.revealIgnored match
3971-
case SelectionProto(nme.apply, mpt, _, _) =>
3971+
case SelectionProto(nme.apply, mpt, _, _, _) =>
39723972
mpt.revealIgnored match
39733973
case fpt: FunProto => fpt
39743974
case _ => pt

Diff for: presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala

+10
Original file line numberDiff line numberDiff line change
@@ -401,3 +401,13 @@ class PcDefinitionSuite extends BasePcDefinitionSuite:
401401
|
402402
|""".stripMargin
403403
)
404+
405+
@Test def `implicit-extension` =
406+
check(
407+
"""|class MyIntOut(val value: Int)
408+
|object MyIntOut:
409+
| extension (i: MyIntOut) def <<uneven>> = i.value % 2 == 1
410+
|
411+
|val a = MyIntOut(1).un@@even
412+
|""".stripMargin,
413+
)

Diff for: presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala

+31
Original file line numberDiff line numberDiff line change
@@ -1143,3 +1143,34 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite:
11431143
| case MySome[<<AA>>](value: <<A@@A>>) extends MyOption[Int]
11441144
|""".stripMargin,
11451145
)
1146+
1147+
@Test def `implicit-extension` =
1148+
check(
1149+
"""|class MyIntOut(val value: Int)
1150+
|object MyIntOut:
1151+
| extension (i: MyIntOut) def <<uneven>> = i.value % 2 == 1
1152+
|
1153+
|val a = MyIntOut(1)
1154+
|val m = a.<<un@@even>>
1155+
|""".stripMargin,
1156+
)
1157+
1158+
@Test def `implicit-extension-2` =
1159+
check(
1160+
"""|class MyIntOut(val value: Int)
1161+
|object MyIntOut:
1162+
| extension (i: MyIntOut) def <<uneven>>(u: Int) = i.value % 2 == 1
1163+
|
1164+
|val a = MyIntOut(1).<<un@@even>>(3)
1165+
|""".stripMargin,
1166+
)
1167+
1168+
@Test def `implicit-extension-infix` =
1169+
check(
1170+
"""|class MyIntOut(val value: Int)
1171+
|object MyIntOut:
1172+
| extension (i: MyIntOut) def <<++>>(u: Int) = i.value + u
1173+
|
1174+
|val a = MyIntOut(1) <<+@@+>> 3
1175+
|""".stripMargin,
1176+
)

Diff for: presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala

+12
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,15 @@ class HoverTypeSuite extends BaseHoverSuite:
355355
"""|val ddd: Int
356356
|""".stripMargin.hover,
357357
)
358+
359+
@Test def `infix-extension` =
360+
check(
361+
"""|class MyIntOut(val value: Int)
362+
|object MyIntOut:
363+
| extension (i: MyIntOut) def uneven = i.value % 2 == 1
364+
|
365+
|val a = MyIntOut(1).un@@even
366+
|""".stripMargin,
367+
"""|extension (i: MyIntOut) def uneven: Boolean
368+
|""".stripMargin.hover,
369+
)

Diff for: tests/semanticdb/expect/Extension.expect.scala

+9
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,12 @@ extension (s/*<-ext::Extension$package.readInto().(s)*/: String/*->scala::Predef
1616

1717
trait Functor/*<-ext::Functor#*/[F/*<-ext::Functor#[F]*/[_]]:
1818
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]*/]
19+
20+
opaque type Deck/*<-ext::Extension$package.Deck#*/ = Long/*->scala::Long#*/
21+
object Deck/*<-ext::Extension$package.Deck.*/:
22+
extension (data/*<-ext::Extension$package.Deck.fooSize().(data)*/: Deck/*->ext::Extension$package.Deck#*/)
23+
def fooSize/*<-ext::Extension$package.Deck.fooSize().*/: Int/*->scala::Int#*/ = ???/*->scala::Predef.`???`().*/
24+
25+
object DeckUsage/*<-ext::DeckUsage.*/:
26+
val deck/*<-ext::DeckUsage.deck.*/: Deck/*->ext::Extension$package.Deck#*/ = ???/*->scala::Predef.`???`().*/
27+
deck/*->ext::DeckUsage.deck.*/.fooSize/*->ext::Extension$package.Deck.fooSize().*/

Diff for: tests/semanticdb/expect/Extension.scala

+9
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,12 @@ extension (s: String)
1616

1717
trait Functor[F[_]]:
1818
extension [T](t: F[T]) def map[U](f: T => U): F[U]
19+
20+
opaque type Deck = Long
21+
object Deck:
22+
extension (data: Deck)
23+
def fooSize: Int = ???
24+
25+
object DeckUsage:
26+
val deck: Deck = ???
27+
deck.fooSize

Diff for: tests/semanticdb/metac.expect

+23-3
Original file line numberDiff line numberDiff line change
@@ -1450,12 +1450,18 @@ Schema => SemanticDB v4
14501450
Uri => Extension.scala
14511451
Text => empty
14521452
Language => Scala
1453-
Symbols => 26 entries
1454-
Occurrences => 52 entries
1453+
Symbols => 32 entries
1454+
Occurrences => 66 entries
14551455
Synthetics => 1 entries
14561456

14571457
Symbols:
1458-
ext/Extension$package. => final package object ext extends Object { self: ext.type => +6 decls }
1458+
ext/DeckUsage. => final object DeckUsage extends Object { self: DeckUsage.type => +2 decls }
1459+
ext/DeckUsage.deck. => val method deck Deck
1460+
ext/Extension$package. => final package object ext extends Object { self: ext.type { opaque type Deck } => +9 decls }
1461+
ext/Extension$package.Deck# => opaque type Deck
1462+
ext/Extension$package.Deck. => final object Deck extends Object { self: Deck.type => +2 decls }
1463+
ext/Extension$package.Deck.fooSize(). => method fooSize (param data: Deck): Int
1464+
ext/Extension$package.Deck.fooSize().(data) => param data: Deck
14591465
ext/Extension$package.`#*#`(). => method #*# (param s: String)(param i: Int): Tuple2[String, Int]
14601466
ext/Extension$package.`#*#`().(i) => param i: Int
14611467
ext/Extension$package.`#*#`().(s) => param s: String
@@ -1535,6 +1541,20 @@ Occurrences:
15351541
[17:44..17:45): U -> ext/Functor#map().[U]
15361542
[17:48..17:49): F -> ext/Functor#[F]
15371543
[17:50..17:51): U -> ext/Functor#map().[U]
1544+
[19:12..19:16): Deck <- ext/Extension$package.Deck#
1545+
[19:19..19:23): Long -> scala/Long#
1546+
[20:7..20:11): Deck <- ext/Extension$package.Deck.
1547+
[21:13..21:17): data <- ext/Extension$package.Deck.fooSize().(data)
1548+
[21:19..21:23): Deck -> ext/Extension$package.Deck#
1549+
[22:8..22:15): fooSize <- ext/Extension$package.Deck.fooSize().
1550+
[22:17..22:20): Int -> scala/Int#
1551+
[22:23..22:26): ??? -> scala/Predef.`???`().
1552+
[24:7..24:16): DeckUsage <- ext/DeckUsage.
1553+
[25:6..25:10): deck <- ext/DeckUsage.deck.
1554+
[25:12..25:16): Deck -> ext/Extension$package.Deck#
1555+
[25:19..25:22): ??? -> scala/Predef.`???`().
1556+
[26:2..26:6): deck -> ext/DeckUsage.deck.
1557+
[26:7..26:14): fooSize -> ext/Extension$package.Deck.fooSize().
15381558

15391559
Synthetics:
15401560
[14:46..14:61):summon[Read[T]] => *(x$2)

0 commit comments

Comments
 (0)