Skip to content

Commit bf7009e

Browse files
authored
Merge pull request #12979 from dotty-staging/fix-12949
2 parents 094a3c5 + 733200e commit bf7009e

23 files changed

+119
-38
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,7 @@ object desugar {
783783
DefDef(
784784
className.toTermName, joinParams(constrTparams, defParamss),
785785
classTypeRef, creatorExpr)
786-
.withMods(companionMods | mods.flags.toTermFlags & GivenOrImplicit | Synthetic | Final)
786+
.withMods(companionMods | mods.flags.toTermFlags & GivenOrImplicit | Final)
787787
.withSpan(cdef.span) :: Nil
788788
}
789789

@@ -809,7 +809,7 @@ object desugar {
809809
Nil
810810
}
811811
}
812-
val classMods = if mods.is(Given) then mods &~ Given | Synthetic else mods
812+
val classMods = if mods.is(Given) then mods | Synthetic else mods
813813
cpy.TypeDef(cdef: TypeDef)(
814814
name = className,
815815
rhs = cpy.Template(impl)(constr, parents1, clsDerived, self1,

compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class TreeMapWithImplicits extends tpd.TreeMap {
6363
private def nestedScopeCtx(defs: List[Tree])(using Context): Context = {
6464
val nestedCtx = ctx.fresh.setNewScope
6565
defs foreach {
66-
case d: DefTree if d.symbol.isOneOf(GivenOrImplicit) => nestedCtx.enter(d.symbol)
66+
case d: DefTree if d.symbol.isOneOf(GivenOrImplicitVal) => nestedCtx.enter(d.symbol)
6767
case _ =>
6868
}
6969
nestedCtx
@@ -74,7 +74,7 @@ class TreeMapWithImplicits extends tpd.TreeMap {
7474
new TreeTraverser {
7575
def traverse(tree: Tree)(using Context): Unit = {
7676
tree match {
77-
case d: DefTree if d.symbol.isOneOf(GivenOrImplicit) =>
77+
case d: DefTree if d.symbol.isOneOf(GivenOrImplicitVal) =>
7878
nestedCtx.enter(d.symbol)
7979
case _ =>
8080
}

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import Scopes._
1313
import Uniques._
1414
import ast.Trees._
1515
import ast.untpd
16-
import Flags.GivenOrImplicit
1716
import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, ReusableInstance}
1817
import typer.{Implicits, ImportInfo, Inliner, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables}
1918
import Nullables._

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ object Flags {
233233
val (Param @ _, TermParam @ _, TypeParam @ _) = newFlags(8, "<param>")
234234

235235
/** Labeled with `implicit` modifier (implicit value) */
236-
val (Implicit @ _, ImplicitTerm @ _, _) = newFlags(9, "implicit")
236+
val (Implicit @ _, ImplicitVal @ _, _) = newFlags(9, "implicit")
237237

238238
/** Labeled with `lazy` (a lazy val) / a trait */
239239
val (LazyOrTrait @ _, Lazy @ _, Trait @ _) = newFlags(10, "lazy", "<trait>")
@@ -321,7 +321,7 @@ object Flags {
321321
val (Extension @ _, ExtensionMethod @ _, _) = newFlags(28, "<extension>")
322322

323323
/** An inferable (`given`) parameter */
324-
val (Given @ _, _, _) = newFlags(29, "given")
324+
val (Given @ _, GivenVal @ _, _) = newFlags(29, "given")
325325

326326
/** Symbol is defined by a Java class */
327327
val (JavaDefined @ _, JavaDefinedVal @ _, _) = newFlags(30, "<java>")
@@ -568,6 +568,7 @@ object Flags {
568568
val FinalOrSealed: FlagSet = Final | Sealed
569569
val GivenOrImplicit: FlagSet = Given | Implicit
570570
val GivenOrImplicitVal: FlagSet = GivenOrImplicit.toTermFlags
571+
val GivenMethod: FlagSet = Given | Method
571572
val InlineOrProxy: FlagSet = Inline | InlineProxy // An inline method or inline argument proxy */
572573
val InlineMethod: FlagSet = Inline | Method
573574
val InlineParam: FlagSet = Inline | Param
@@ -600,7 +601,6 @@ object Flags {
600601
val Scala2Trait: FlagSet = Scala2x | Trait
601602
val SyntheticArtifact: FlagSet = Synthetic | Artifact
602603
val SyntheticCase: FlagSet = Synthetic | Case
603-
val SyntheticGivenMethod: FlagSet = Synthetic | Given | Method
604604
val SyntheticModule: FlagSet = Synthetic | Module
605605
val SyntheticOpaque: FlagSet = Synthetic | Opaque
606606
val SyntheticParam: FlagSet = Synthetic | Param

compiler/src/dotty/tools/dotc/core/Scopes.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ object Scopes {
411411
var irefs = new mutable.ListBuffer[TermRef]
412412
var e = lastEntry
413413
while (e ne null) {
414-
if (e.sym.isOneOf(GivenOrImplicit)) {
414+
if (e.sym.isOneOf(GivenOrImplicitVal)) {
415415
val d = e.sym.denot
416416
irefs += TermRef(NoPrefix, d.symbol.asTerm).withDenot(d)
417417
}

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,19 @@ object SymDenotations {
711711
}
712712
)
713713

714+
/** Do this symbol and `cls` represent a pair of a given or implicit method and
715+
* its associated class that were defined by a single definition?
716+
* This can mean one of two things:
717+
* - the method and class are defined in a structural given instance, or
718+
* - the class is an implicit class and the method is its implicit conversion.
719+
*/
720+
final def isCoDefinedGiven(cls: Symbol)(using Context): Boolean =
721+
is(Method) && isOneOf(GivenOrImplicit)
722+
&& ( is(Synthetic) // previous scheme used in 3.0
723+
|| cls.isOneOf(GivenOrImplicit) // new scheme from 3.1
724+
)
725+
&& name == cls.name.toTermName && owner == cls.owner
726+
714727
/** Is this a denotation of a stable term (or an arbitrary type)?
715728
* Terms are stable if they are idempotent (as in TreeInfo.Idempotent): that is, they always return the same value,
716729
* if any.
@@ -2231,7 +2244,7 @@ object SymDenotations {
22312244
if (keepOnly eq implicitFilter)
22322245
if (this.is(Package)) Iterator.empty
22332246
// implicits in package objects are added by the overriding `memberNames` in `PackageClassDenotation`
2234-
else info.decls.iterator.filter(_.isOneOf(GivenOrImplicit))
2247+
else info.decls.iterator.filter(_.isOneOf(GivenOrImplicitVal))
22352248
else info.decls.iterator
22362249
for (sym <- ownSyms) maybeAdd(sym.name)
22372250
names

compiler/src/dotty/tools/dotc/core/TypeErrors.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class CyclicReference private (val denot: SymDenotation) extends TypeError {
141141
}
142142

143143
// Give up and give generic errors.
144-
else if (cycleSym.isOneOf(GivenOrImplicit, butNot = Method) && cycleSym.owner.isTerm)
144+
else if (cycleSym.isOneOf(GivenOrImplicitVal, butNot = Method) && cycleSym.owner.isTerm)
145145
CyclicReferenceInvolvingImplicit(cycleSym)
146146
else
147147
CyclicReferenceInvolving(denot)

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -722,9 +722,9 @@ class TreePickler(pickler: TastyPickler) {
722722
if flags.is(Invisible) then writeModTag(INVISIBLE)
723723
if (flags.is(Erased)) writeModTag(ERASED)
724724
if (flags.is(Exported)) writeModTag(EXPORTED)
725+
if (flags.is(Given)) writeModTag(GIVEN)
726+
if (flags.is(Implicit)) writeModTag(IMPLICIT)
725727
if (isTerm) {
726-
if (flags.is(Implicit)) writeModTag(IMPLICIT)
727-
if (flags.is(Given)) writeModTag(GIVEN)
728728
if (flags.is(Lazy, butNot = Module)) writeModTag(LAZY)
729729
if (flags.is(AbsOverride)) { writeModTag(ABSTRACT); writeModTag(OVERRIDE) }
730730
if (flags.is(Mutable)) writeModTag(MUTABLE)

compiler/src/dotty/tools/dotc/interactive/Completion.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ object Completion {
323323
val extMethodsFromImplicitScope = extractMemberExtensionMethods(implicitScopeCompanions)
324324

325325
// 4. The reference is of the form r.m and the extension method is defined in some given instance in the implicit scope of the type of r.
326-
val givensInImplicitScope = implicitScopeCompanions.flatMap(_.membersBasedOnFlags(required = Given, excluded = EmptyFlags)).map(_.info)
326+
val givensInImplicitScope = implicitScopeCompanions.flatMap(_.membersBasedOnFlags(required = GivenVal, excluded = EmptyFlags)).map(_.info)
327327
val extMethodsFromGivensInImplicitScope = extractMemberExtensionMethods(givensInImplicitScope)
328328

329329
val availableExtMethods = extMethodsFromGivensInImplicitScope ++ extMethodsFromImplicitScope ++ extMethodsFromGivensInScope ++ extMethodsInScope

compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ class ExtractSemanticDB extends Phase:
7979
|| sym.isLocalDummy
8080
|| sym.is(Synthetic)
8181
|| sym.isSetter
82+
|| sym.isOldStyleImplicitConversion(forImplicitClassOnly = true)
83+
|| sym.owner.isGivenInstanceSummoner
8284
|| excludeDefOrUse(sym)
8385

8486
private def excludeDefOrUse(sym: Symbol)(using Context): Boolean =
@@ -102,6 +104,7 @@ class ExtractSemanticDB extends Phase:
102104
private def excludeChildren(sym: Symbol)(using Context): Boolean =
103105
!sym.exists
104106
|| sym.is(Param) && sym.info.bounds.hi.isInstanceOf[Types.HKTypeLambda]
107+
|| sym.isOldStyleImplicitConversion(forImplicitClassOnly = true)
105108

106109
/** Uses of this symbol where the reference has given span should be excluded from semanticdb */
107110
private def excludeUse(qualifier: Option[Symbol], sym: Symbol)(using Context): Boolean =

compiler/src/dotty/tools/dotc/semanticdb/Tools.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ object Tools:
4141
document.copy(text = text)
4242
end loadTextDocument
4343

44+
def loadTextDocumentUnsafe(scalaAbsolutePath: Path, semanticdbAbsolutePath: Path): TextDocument =
45+
val docs = parseTextDocuments(semanticdbAbsolutePath).documents
46+
assert(docs.length == 1)
47+
docs.head.copy(text = new String(Files.readAllBytes(scalaAbsolutePath), StandardCharsets.UTF_8))
48+
4449
/** Parses SemanticDB text documents from an absolute path to a `*.semanticdb` file. */
4550
private def parseTextDocuments(path: Path): TextDocuments =
4651
val bytes = Files.readAllBytes(path) // NOTE: a semanticdb file is a TextDocuments message, not TextDocument

compiler/src/dotty/tools/dotc/transform/SymUtils.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,31 @@ object SymUtils:
8787

8888
def isGenericProduct(using Context): Boolean = whyNotGenericProduct.isEmpty
8989

90+
/** Is this an old style implicit conversion?
91+
* @param directOnly only consider explicitly written methods
92+
* @param forImplicitClassOnly only consider methods generated from implicit classes
93+
*/
94+
def isOldStyleImplicitConversion(directOnly: Boolean = false, forImplicitClassOnly: Boolean = false)(using Context): Boolean =
95+
self.is(Implicit) && self.info.stripPoly.match
96+
case mt @ MethodType(_ :: Nil) if !mt.isImplicitMethod =>
97+
if self.isCoDefinedGiven(mt.finalResultType.typeSymbol)
98+
then !directOnly
99+
else !forImplicitClassOnly
100+
case _ =>
101+
false
102+
103+
/** Is this the method that summons a structural given instance? */
104+
def isGivenInstanceSummoner(using Context): Boolean =
105+
def isCodefined(info: Type): Boolean = info.stripPoly match
106+
case mt: MethodType =>
107+
// given summoner can only have contextual params
108+
mt.isImplicitMethod && isCodefined(mt.resultType)
109+
case mt: ExprType =>
110+
isCodefined(mt.resultType)
111+
case res =>
112+
self.isCoDefinedGiven(res.typeSymbol)
113+
self.isAllOf(Given | Method) && isCodefined(self.info)
114+
90115
def useCompanionAsMirror(using Context): Boolean = self.linkedClass.exists && !self.is(Scala2x)
91116

92117
/** Is this a sealed class or trait for which a sum mirror is generated?

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,7 +1631,7 @@ trait Applications extends Compatibility {
16311631
/** Widen the result type of synthetic given methods from the implementation class to the
16321632
* type that's implemented. Example
16331633
*
1634-
* given I[X] as T { ... }
1634+
* given I[X]: T with { ... }
16351635
*
16361636
* This desugars to
16371637
*
@@ -1641,7 +1641,7 @@ trait Applications extends Compatibility {
16411641
* To compare specificity we should compare with `T`, not with its implementation `I[X]`.
16421642
* No such widening is performed for given aliases, which are not synthetic. E.g.
16431643
*
1644-
* given J[X] as T = rhs
1644+
* given J[X]: T = rhs
16451645
*
16461646
* already has the right result type `T`. Neither is widening performed for given
16471647
* objects, since these are anyway taken to be more specific than methods
@@ -1652,8 +1652,8 @@ trait Applications extends Compatibility {
16521652
mt.derivedLambdaType(mt.paramNames, mt.paramInfos, widenGiven(mt.resultType, alt))
16531653
case pt: PolyType =>
16541654
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, widenGiven(pt.resultType, alt))
1655-
case _ =>
1656-
if (alt.symbol.isAllOf(SyntheticGivenMethod)) tp.widenToParents
1655+
case rt =>
1656+
if alt.symbol.isCoDefinedGiven(rt.typeSymbol) then tp.widenToParents
16571657
else tp
16581658
}
16591659

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ object Checking {
473473
if (sym.is(Implicit)) {
474474
if (sym.owner.is(Package))
475475
fail(TopLevelCantBeImplicit(sym))
476-
if (sym.isType)
476+
if sym.isType && (!sym.isClass || sym.is(Trait)) then
477477
fail(TypesAndTraitsCantBeImplicit())
478478
}
479479
if sym.is(Transparent) then
@@ -861,22 +861,14 @@ trait Checking {
861861
/** If `sym` is an old-style implicit conversion, check that implicit conversions are enabled.
862862
* @pre sym.is(GivenOrImplicit)
863863
*/
864-
def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = {
865-
def check(): Unit =
864+
def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit =
865+
if sym.isOldStyleImplicitConversion(directOnly = true) then
866866
checkFeature(
867867
nme.implicitConversions,
868868
i"Definition of implicit conversion $sym",
869869
ctx.owner.topLevelClass,
870870
sym.srcPos)
871871

872-
sym.info.stripPoly match {
873-
case mt @ MethodType(_ :: Nil)
874-
if !mt.isImplicitMethod && !sym.is(Synthetic) => // it's an old-styleconversion
875-
check()
876-
case _ =>
877-
}
878-
}
879-
880872
/** If `tree` is an application of a new-style implicit conversion (using the apply
881873
* method of a `scala.Conversion` instance), check that implicit conversions are
882874
* enabled.

compiler/src/dotty/tools/dotc/typer/ImportInfo.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ class ImportInfo(symf: Context ?=> Symbol,
151151
else
152152
for
153153
renamed <- reverseMapping.keys
154-
denot <- pre.member(reverseMapping(renamed)).altsWith(_.isOneOf(GivenOrImplicit))
154+
denot <- pre.member(reverseMapping(renamed)).altsWith(_.isOneOf(GivenOrImplicitVal))
155155
yield
156156
val original = reverseMapping(renamed)
157157
val ref = TermRef(pre, original, denot)

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ class Namer { typer: Typer =>
241241

242242
tree match {
243243
case tree: TypeDef if tree.isClassDef =>
244-
val flags = checkFlags(tree.mods.flags &~ Implicit)
244+
val flags = checkFlags(tree.mods.flags)
245245
val name = checkNoConflict(tree.name, flags.is(Private), tree.span).asTypeName
246246
val cls =
247247
createOrRefine[ClassSymbol](tree, name, flags, ctx.owner,

compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ trait QuotesAndSplices {
326326
tdef.symbol.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_fromAboveAnnot.typeRef)).withSpan(tdef.span)))
327327
val bindingType = getBinding(tdef.symbol).symbol.typeRef
328328
val bindingTypeTpe = AppliedType(defn.QuotedTypeClass.typeRef, bindingType :: Nil)
329-
val sym = newPatternBoundSymbol(nameOfSyntheticGiven, bindingTypeTpe, tdef.span, flags = ImplicitTerm)(using ctx0)
329+
val sym = newPatternBoundSymbol(nameOfSyntheticGiven, bindingTypeTpe, tdef.span, flags = ImplicitVal)(using ctx0)
330330
buff += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingTypeTpe)).withSpan(tdef.span)
331331
super.transform(tdef)
332332
}

compiler/test-resources/repl/i1374

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
scala> implicit class Padder(val sb: StringBuilder) extends AnyVal { def pad2(width: Int) = { 1 to width - sb.length foreach { sb append '*' }; sb } }
22
// defined class Padder
3+
def Padder(sb: StringBuilder): Padder
34
scala> val greeting = new StringBuilder("Hello, kitteh!")
45
val greeting: StringBuilder = Hello, kitteh!
56
scala> val a = greeting pad2 20

compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ import dotty.tools.dotc.util.SourceFile
2525
@main def updateExpect =
2626
SemanticdbTests().runExpectTest(updateExpectFiles = true)
2727

28+
/** Useful for printing semanticdb metac output for one file
29+
*
30+
* @param root the output directory containing semanticdb output,
31+
* only 1 semanticdb file should be present
32+
* @param source the single source file producing the semanticdb
33+
*/
34+
@main def metac(root: String, source: String) =
35+
val rootSrc = Paths.get(root)
36+
val sourceSrc = Paths.get(source)
37+
val semanticFile = FileSystems.getDefault.getPathMatcher("glob:**.semanticdb")
38+
def inputFile(): Path =
39+
val ls = Files.walk(rootSrc.resolve("META-INF").resolve("semanticdb"))
40+
val files =
41+
try ls.filter(p => semanticFile.matches(p)).collect(Collectors.toList).asScala
42+
finally ls.close()
43+
require(files.sizeCompare(1) == 0, s"No semanticdb files! $rootSrc")
44+
files.head
45+
val metacSb: StringBuilder = StringBuilder(5000)
46+
val semanticdbPath = inputFile()
47+
val doc = Tools.loadTextDocumentUnsafe(sourceSrc.toAbsolutePath, semanticdbPath)
48+
Tools.metac(doc, Paths.get(doc.uri))(using metacSb)
49+
Files.write(rootSrc.resolve("metac.expect"), metacSb.toString.getBytes(StandardCharsets.UTF_8))
50+
51+
2852
@Category(Array(classOf[BootstrappedOnlyTests]))
2953
class SemanticdbTests:
3054
val javaFile = FileSystems.getDefault.getPathMatcher("glob:**.java")

tests/pos/i12949.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
object Catch22:
2+
trait TC[V]
3+
object TC:
4+
export Hodor.TC.given
5+
6+
object Hodor:
7+
object TC:
8+
import Catch22.TC
9+
given fromString[V <: String]: TC[V] = ???
10+
transparent inline given fromDouble[V <: Double]: TC[V] =
11+
new TC[V]:
12+
type Out = Double
13+
given fromInt[V <: Int]: TC[V] with
14+
type Out = Int
15+
16+
object Test:
17+
summon[Catch22.TC["hi"]] //works
18+
summon[Catch22.TC[7.7]] //works
19+
summon[Catch22.TC[1]] //error

tests/semanticdb/expect/InventedNames.expect.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ val f/*<-givens::InventedNames$package.f.*/ = given_Float/*->givens::InventedNam
3939
val g/*<-givens::InventedNames$package.g.*/ = `* *`/*->givens::InventedNames$package.`* *`.*/
4040
val x/*<-givens::InventedNames$package.x.*/ = given_X/*->givens::InventedNames$package.given_X.*/
4141
val y/*<-givens::InventedNames$package.y.*/ = given_Y/*->givens::InventedNames$package.given_Y().*/
42-
val z/*<-givens::InventedNames$package.z.*/ = given_Z_T/*->givens::InventedNames$package.given_Z_T().*/[String/*->scala::Predef.String#*/]
42+
val z/*<-givens::InventedNames$package.z.*/ = given_Z_T/*->givens::InventedNames$package.given_Z_T().*/[String/*->scala::Predef.String#*/]

tests/semanticdb/expect/InventedNames.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ val f = given_Float
3939
val g = `* *`
4040
val x = given_X
4141
val y = given_Y
42-
val z = given_Z_T[String]
42+
val z = given_Z_T[String]

0 commit comments

Comments
 (0)