diff --git a/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala b/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala index 61d1ff0adb9a..e3b5a98cc884 100644 --- a/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala +++ b/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala @@ -41,17 +41,21 @@ class QuoteDriver extends Driver { method.invoke(inst).asInstanceOf[T] } - def show(expr: Expr[_], settings: Toolbox.Settings): String = { - def show(tree: Tree, ctx: Context): String = { - implicit val c: Context = ctx - val tree1 = - if (ctx.settings.YshowRawQuoteTrees.value) tree - else (new TreeCleaner).transform(tree) - ReflectionImpl.showTree(tree1) - } - withTree(expr, show, settings) + private def doShow(tree: Tree, ctx: Context): String = { + implicit val c: Context = ctx + val tree1 = + if (ctx.settings.YshowRawQuoteTrees.value) tree + else (new TreeCleaner).transform(tree) + ReflectionImpl.showTree(tree1) } + def show(expr: Expr[_], settings: Toolbox.Settings): String = + withTree(expr, doShow, settings) + + def show(tpe: Type[_], settings: Toolbox.Settings): String = + withTypeTree(tpe, doShow, settings) + + def withTree[T](expr: Expr[_], f: (Tree, Context) => T, settings: Toolbox.Settings): T = { val ctx = setToolboxSettings(setup(settings.compilerArgs.toArray :+ "dummy.scala", initCtx.fresh)._2.fresh, settings) @@ -65,7 +69,7 @@ class QuoteDriver extends Driver { } def withTypeTree[T](tpe: Type[_], f: (TypTree, Context) => T, settings: Toolbox.Settings): T = { - val (_, ctx: Context) = setup(settings.compilerArgs.toArray :+ "dummy.scala", initCtx.fresh) + val ctx = setToolboxSettings(setup(settings.compilerArgs.toArray :+ "dummy.scala", initCtx.fresh)._2.fresh, settings) var output: Option[T] = None def registerTree(tree: tpd.Tree)(ctx: Context): Unit = { diff --git a/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala b/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala index 1067caf89a9a..3e48fd0bd649 100644 --- a/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala +++ b/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala @@ -2,7 +2,7 @@ package dotty.tools.dotc.quoted import dotty.tools.dotc.ast.tpd -import scala.quoted.Expr +import scala.quoted._ import scala.quoted.Exprs.{LiftedExpr, TastyTreeExpr} /** Default runners for quoted expressions */ @@ -24,5 +24,6 @@ object ToolboxImpl { def show[T](expr: Expr[T]): String = synchronized(driver.show(expr, settings)) + def show[T](tpe: Type[T]): String = synchronized(driver.show(tpe, settings)) } } diff --git a/library/src-bootstrapped/scala/quoted/Type.scala b/library/src-bootstrapped/scala/quoted/Type.scala index c324e753ff8e..243b63b3889a 100644 --- a/library/src-bootstrapped/scala/quoted/Type.scala +++ b/library/src-bootstrapped/scala/quoted/Type.scala @@ -6,6 +6,9 @@ import scala.runtime.quoted.Unpickler.Pickled sealed abstract class Type[T <: AnyKind] { type `$splice` = T + + /** Show a source code like representation of this type */ + final def show(implicit toolbox: Toolbox): String = toolbox.show(this.asInstanceOf[Type[Any]]) } /** Some basic type tags, currently incomplete */ diff --git a/library/src-non-bootstrapped/scala/quoted/Type.scala b/library/src-non-bootstrapped/scala/quoted/Type.scala index 90f56da01712..0dfc0f944f16 100644 --- a/library/src-non-bootstrapped/scala/quoted/Type.scala +++ b/library/src-non-bootstrapped/scala/quoted/Type.scala @@ -6,6 +6,9 @@ import scala.runtime.quoted.Unpickler.Pickled sealed abstract class Type[T] { type `$splice` = T + + /** Show a source code like representation of this type */ + final def show(implicit toolbox: Toolbox): String = toolbox.show(this) } /** Some basic type tags, currently incomplete */ diff --git a/library/src/scala/quoted/Toolbox.scala b/library/src/scala/quoted/Toolbox.scala index 7fab42b308de..b57fee97dad7 100644 --- a/library/src/scala/quoted/Toolbox.scala +++ b/library/src/scala/quoted/Toolbox.scala @@ -6,6 +6,7 @@ import scala.annotation.implicitNotFound trait Toolbox { def run[T](expr: Expr[T]): T def show[T](expr: Expr[T]): String + def show[T](tpe: Type[T]): String } object Toolbox { @@ -32,7 +33,7 @@ object Toolbox { } /** Setting of the Toolbox instance. */ - class Settings private (val outDir: Option[String], val showRawTree: Boolean, val compilerArgs: List[String], val color: Boolean) + case class Settings private (outDir: Option[String], showRawTree: Boolean, compilerArgs: List[String], color: Boolean) object Settings { diff --git a/library/src/scala/tasty/reflect/Kernel.scala b/library/src/scala/tasty/reflect/Kernel.scala index 38c605ccfc6b..1b0144ed9335 100644 --- a/library/src/scala/tasty/reflect/Kernel.scala +++ b/library/src/scala/tasty/reflect/Kernel.scala @@ -699,7 +699,7 @@ trait Kernel { // // PATTERNS // - + /** Pattern tree of the pattern part of a CaseDef */ type Pattern <: AnyRef diff --git a/library/src/scala/tasty/reflect/Printers.scala b/library/src/scala/tasty/reflect/Printers.scala index 81c5e3057e17..0f34ce7edc84 100644 --- a/library/src/scala/tasty/reflect/Printers.scala +++ b/library/src/scala/tasty/reflect/Printers.scala @@ -433,7 +433,7 @@ trait Printers (new Buffer).printPattern(pattern).result() def showTypeOrBounds(tpe: TypeOrBounds)(implicit ctx: Context): String = - (new Buffer).printTypeOrBound(tpe).result() + (new Buffer).printTypeOrBound(tpe)(None).result() def showConstant(const: Constant)(implicit ctx: Context): String = (new Buffer).printConstant(const).result() @@ -516,7 +516,7 @@ trait Printers def lineBreak(): String = "\n" + (" " * indent) def doubleLineBreak(): String = "\n\n" + (" " * indent) - def printTree(tree: Tree): Buffer = tree match { + def printTree(tree: Tree)(implicit elideThis: Option[Symbol] = None): Buffer = tree match { case PackageObject(body)=> printTree(body) // Print package object @@ -582,19 +582,19 @@ trait Printers def printParent(parent: Tree /* Term | TypeTree */, needEmptyParens: Boolean = false): Unit = parent match { case IsTypeTree(parent) => - printTypeTree(parent) + printTypeTree(parent)(Some(cdef.symbol)) case IsTerm(Term.TypeApply(fun, targs)) => printParent(fun) case IsTerm(Term.Apply(fun@Term.Apply(_,_), args)) => printParent(fun, true) if (!args.isEmpty || needEmptyParens) - inParens(printTrees(args, ", ")) + inParens(printTrees(args, ", ")(Some(cdef.symbol))) case IsTerm(Term.Apply(fun, args)) => printParent(fun) if (!args.isEmpty || needEmptyParens) - inParens(printTrees(args, ", ")) + inParens(printTrees(args, ", ")(Some(cdef.symbol))) case IsTerm(Term.Select(Term.IsNew(newTree), _)) => - printType(newTree.tpe) + printType(newTree.tpe)(Some(cdef.symbol)) case IsTerm(parent) => throw new MatchError(parent.show) } @@ -646,7 +646,7 @@ trait Printers indented { val name1 = if (name == "_") "this" else name this += " " += highlightValDef(name1, color) += ": " - printTypeTree(tpt) + printTypeTree(tpt)(Some(cdef.symbol)) this += " =>" } } @@ -971,7 +971,7 @@ trait Printers (flatStats.result(), flatExpr) } - def printFlatBlock(stats: List[Statement], expr: Term): Buffer = { + def printFlatBlock(stats: List[Statement], expr: Term)(implicit elideThis: Option[Symbol]): Buffer = { val (stats1, expr1) = flatBlock(stats, expr) // Remove Term.Lambda nodes, lambdas are printed by their definition val stats2 = stats1.filter { case Term.Lambda(_, _) => false; case _ => true } @@ -992,7 +992,7 @@ trait Printers } } - def printStats(stats: List[Tree], expr: Tree): Unit = { + def printStats(stats: List[Tree], expr: Tree)(implicit elideThis: Option[Symbol]): Unit = { def printSeparator(next: Tree): Unit = { // Avoid accidental application of opening `{` on next line with a double break def rec(next: Tree): Unit = next match { @@ -1039,13 +1039,13 @@ trait Printers this } - def printTrees(trees: List[Tree], sep: String): Buffer = - printList(trees, sep, printTree) + def printTrees(trees: List[Tree], sep: String)(implicit elideThis: Option[Symbol]): Buffer = + printList(trees, sep, (t: Tree) => printTree(t)) - def printTypeTrees(trees: List[TypeTree], sep: String): Buffer = - printList(trees, sep, printTypeTree) + def printTypeTrees(trees: List[TypeTree], sep: String)(implicit elideThis: Option[Symbol] = None): Buffer = + printList(trees, sep, (t: TypeTree) => printTypeTree(t)) - def printTypes(trees: List[Type], sep: String): Buffer = { + def printTypes(trees: List[Type], sep: String)(implicit elideThis: Option[Symbol]): Buffer = { def printSeparated(list: List[Type]): Unit = list match { case Nil => case x :: Nil => printType(x) @@ -1111,7 +1111,7 @@ trait Printers this } - def printTypesOrBounds(types: List[TypeOrBounds], sep: String): Buffer = { + def printTypesOrBounds(types: List[TypeOrBounds], sep: String)(implicit elideThis: Option[Symbol]): Buffer = { def printSeparated(list: List[TypeOrBounds]): Unit = list match { case Nil => case x :: Nil => printTypeOrBound(x) @@ -1124,7 +1124,7 @@ trait Printers this } - def printTargsDefs(targs: List[(TypeDef, TypeDef)], isDef:Boolean = true): Unit = { + def printTargsDefs(targs: List[(TypeDef, TypeDef)], isDef:Boolean = true)(implicit elideThis: Option[Symbol]): Unit = { if (!targs.isEmpty) { def printSeparated(list: List[(TypeDef, TypeDef)]): Unit = list match { case Nil => @@ -1139,7 +1139,7 @@ trait Printers } } - def printTargDef(arg: (TypeDef, TypeDef), isMember: Boolean = false, isDef:Boolean = true): Buffer = { + def printTargDef(arg: (TypeDef, TypeDef), isMember: Boolean = false, isDef:Boolean = true)(implicit elideThis: Option[Symbol]): Buffer = { val (argDef, argCons) = arg if (isDef) { @@ -1189,7 +1189,7 @@ trait Printers } } - def printArgsDefs(args: List[ValDef]): Unit = inParens { + def printArgsDefs(args: List[ValDef])(implicit elideThis: Option[Symbol]): Unit = inParens { args match { case Nil => case arg :: _ => @@ -1209,7 +1209,7 @@ trait Printers printSeparated(args) } - def printAnnotations(trees: List[Term]): Buffer = { + def printAnnotations(trees: List[Term])(implicit elideThis: Option[Symbol]): Buffer = { def printSeparated(list: List[Term]): Unit = list match { case Nil => case x :: Nil => printAnnotation(x) @@ -1222,7 +1222,7 @@ trait Printers this } - def printParamDef(arg: ValDef): Unit = { + def printParamDef(arg: ValDef)(implicit elideThis: Option[Symbol]): Unit = { val name = arg.name arg.symbol.owner match { case IsDefDefSymbol(sym) if sym.name == "" => @@ -1260,7 +1260,7 @@ trait Printers indented { caseDef.rhs match { case Term.Block(stats, expr) => - printStats(stats, expr) + printStats(stats, expr)(None) case body => this += lineBreak() printTree(body) @@ -1334,7 +1334,7 @@ trait Printers this += highlightLiteral("'" + v.name, color) } - def printTypeOrBoundsTree(tpt: Tree /*TypeTree | TypeBoundsTree*/): Buffer = tpt match { + def printTypeOrBoundsTree(tpt: Tree)(implicit elideThis: Option[Symbol] = None): Buffer = tpt match { case TypeBoundsTree(lo, hi) => this += "_ >: " printTypeTree(lo) @@ -1346,7 +1346,15 @@ trait Printers printTypeTree(tpt) } - def printTypeTree(tree: TypeTree): Buffer = tree match { + /** Print type tree + * + * @param elideThis Shoud printing elide `C.this` for the given class `C`? + * None means no eliding. + * + * Self type annotation and types in parent list should elide current class + * prefix `C.this` to avoid type checking errors. + */ + def printTypeTree(tree: TypeTree)(implicit elideThis: Option[Symbol] = None): Buffer = tree match { case TypeTree.Inferred() => // TODO try to move this logic into `printType` def printTypeAndAnnots(tpe: Type): Buffer = tpe match { @@ -1360,6 +1368,9 @@ trait Printers case tpe @ Type.SymRef(IsClassDefSymbol(sym), _) if sym.name.endsWith("$") => printType(tpe) this += ".type" + case tpe @ Type.SymRef(sym, _) if sym.isTerm => + printType(tpe) + this += ".type" case tpe => printType(tpe) } printTypeAndAnnots(tree.tpe) @@ -1429,7 +1440,7 @@ trait Printers } - def printTypeOrBound(tpe: TypeOrBounds): Buffer = tpe match { + def printTypeOrBound(tpe: TypeOrBounds)(implicit elideThis: Option[Symbol]): Buffer = tpe match { case tpe@TypeBounds(lo, hi) => this += "_ >: " printType(lo) @@ -1438,11 +1449,19 @@ trait Printers case IsType(tpe) => printType(tpe) } - def printType(tpe: Type): Buffer = tpe match { + /** Print type + * + * @param elideThis Shoud printing elide `C.this` for the given class `C`? + * None means no eliding. + * + * Self type annotation and types in parent list should elide current class + * prefix `C.this` to avoid type checking errors. + */ + def printType(tpe: Type)(implicit elideThis: Option[Symbol] = None): Buffer = tpe match { case Type.ConstantType(const) => printConstant(const) - case Type.SymRef(sym, prefix) => + case Type.SymRef(sym, prefix) if sym.isType => prefix match { case Type.ThisType(Types.EmptyPackage() | Types.RootPackage()) => case NoPrefix() => @@ -1455,14 +1474,25 @@ trait Printers case IsType(prefix @ Type.SymRef(IsClassDefSymbol(_), _)) => printType(prefix) this += "#" + case IsType(Type.ThisType(Type.SymRef(cdef, _))) + if elideThis.nonEmpty && cdef == elideThis.get => case IsType(prefix) => - if (!sym.flags.is(Flags.Local)) { - printType(prefix) - this += "." - } + printType(prefix) + this += "." } this += highlightTypeDef(sym.name.stripSuffix("$"), color) + case Type.SymRef(sym, prefix) if sym.isTerm => + prefix match { + case NoPrefix() | Type.ThisType(Types.EmptyPackage() | Types.RootPackage()) => + this += highlightTypeDef(sym.name, color) + case _ => + printTypeOrBound(prefix) + if (sym.name != "package") + this += "." += highlightTypeDef(sym.name, color) + this + } + case Type.TermRef(name, prefix) => prefix match { case Type.ThisType(Types.EmptyPackage()) => @@ -1578,7 +1608,7 @@ trait Printers case PackageDef(name, _) => this += highlightTypeDef(name, color) } - def printAnnotation(annot: Term): Buffer = { + def printAnnotation(annot: Term)(implicit elideThis: Option[Symbol]): Buffer = { val Annotation(ref, args) = annot this += "@" printTypeTree(ref) @@ -1588,7 +1618,7 @@ trait Printers inParens(printTrees(args, ", ")) } - def printDefAnnotations(definition: Definition): Buffer = { + def printDefAnnotations(definition: Definition)(implicit elideThis: Option[Symbol]): Buffer = { val annots = definition.symbol.annots.filter { case Annotation(annot, _) => annot.tpe match { @@ -1603,7 +1633,7 @@ trait Printers else this } - def printRefinement(tpe: Type): Buffer = { + def printRefinement(tpe: Type)(implicit elideThis: Option[Symbol]): Buffer = { def printMethodicType(tp: TypeOrBounds): Unit = tp match { case tp @ Type.MethodType(paramNames, params, res) => inParens(printMethodicTypeParams(paramNames, params)) @@ -1643,7 +1673,7 @@ trait Printers this += lineBreak() += "}" } - def printMethodicTypeParams(paramNames: List[String], params: List[TypeOrBounds]): Unit = { + def printMethodicTypeParams(paramNames: List[String], params: List[TypeOrBounds])(implicit elideThis: Option[Symbol]): Unit = { def printInfo(info: TypeOrBounds) = info match { case IsTypeBounds(info) => printBounds(info) case IsType(info) => @@ -1664,7 +1694,7 @@ trait Printers printSeparated(paramNames.zip(params)) } - def printBoundsTree(bounds: TypeBoundsTree): Buffer = { + def printBoundsTree(bounds: TypeBoundsTree)(implicit elideThis: Option[Symbol]): Buffer = { bounds.low match { case TypeTree.Inferred() => case low => @@ -1679,7 +1709,7 @@ trait Printers } } - def printBounds(bounds: TypeBounds): Buffer = { + def printBounds(bounds: TypeBounds)(implicit elideThis: Option[Symbol]): Buffer = { this += " >: " printType(bounds.low) this += " <: " diff --git a/library/src/scala/tasty/reflect/SymbolOps.scala b/library/src/scala/tasty/reflect/SymbolOps.scala index 5e34d610c4c8..49de26d96bc3 100644 --- a/library/src/scala/tasty/reflect/SymbolOps.scala +++ b/library/src/scala/tasty/reflect/SymbolOps.scala @@ -82,6 +82,9 @@ trait SymbolOps extends Core { def isAnonymousFunction(implicit ctx: Context): Boolean = kernel.Symbol_isAnonymousFunction(self) def isAbstractType(implicit ctx: Context): Boolean = kernel.Symbol_isAbstractType(self) def isClassConstructor(implicit ctx: Context): Boolean = kernel.Symbol_isClassConstructor(self) + + def isType(implicit ctx: Context): Boolean = kernel.matchTypeSymbol(self).isDefined + def isTerm(implicit ctx: Context): Boolean = kernel.matchTermSymbol(self).isDefined } // PackageSymbol diff --git a/tests/pos/i1793.scala b/tests/pos/i1793.scala index fed8a6165699..8de9b99eb73e 100644 --- a/tests/pos/i1793.scala +++ b/tests/pos/i1793.scala @@ -1,7 +1,7 @@ object Test { import scala.ref.WeakReference def unapply[T <: AnyRef](wr: WeakReference[T]): Option[T] = { - val x = wr.underlying.get + val x: T = wr.underlying.get if (x != null) Some(x) else None } } diff --git a/tests/pos/i2104b.decompiled b/tests/pos/i2104b.decompiled index 2e052fbf1e90..620bbc7ab092 100644 --- a/tests/pos/i2104b.decompiled +++ b/tests/pos/i2104b.decompiled @@ -11,13 +11,13 @@ case class Pair[A, B](_1: A, _2: B) { scala.runtime.Statics.finalizeHash(acc, 2) } override def equals(x$0: scala.Any): scala.Boolean = Pair.this.eq(x$0.$asInstanceOf$[java.lang.Object]).||(x$0 match { - case x$0: Pair[A, B] @scala.unchecked => + case x$0: Pair[Pair.this.A, Pair.this.B] @scala.unchecked => Pair.this._1.==(x$0._1).&&(Pair.this._2.==(x$0._2)) case _ => false }) override def toString(): java.lang.String = scala.runtime.ScalaRunTime._toString(Pair.this) - override def canEqual(that: scala.Any): scala.Boolean = that.isInstanceOf[Pair[A, B] @scala.unchecked] + override def canEqual(that: scala.Any): scala.Boolean = that.isInstanceOf[Pair[Pair.this.A, Pair.this.B] @scala.unchecked] override def productArity: scala.Int = 2 override def productPrefix: java.lang.String = "Pair" override def productElement(n: scala.Int): scala.Any = n match { @@ -35,4 +35,4 @@ object Test { case Cons(scala.Some(i), scala.None) => () } -} +} \ No newline at end of file diff --git a/tests/pos/simpleCaseClass-3.decompiled b/tests/pos/simpleCaseClass-3.decompiled index 43171c07de47..a2af5c0c086f 100644 --- a/tests/pos/simpleCaseClass-3.decompiled +++ b/tests/pos/simpleCaseClass-3.decompiled @@ -5,13 +5,13 @@ case class A[T](x: T) { scala.runtime.Statics.finalizeHash(acc, 1) } override def equals(x$0: scala.Any): scala.Boolean = A.this.eq(x$0.$asInstanceOf$[java.lang.Object]).||(x$0 match { - case x$0: A[T] @scala.unchecked => + case x$0: A[A.this.T] @scala.unchecked => A.this.x.==(x$0.x) case _ => false }) override def toString(): java.lang.String = scala.runtime.ScalaRunTime._toString(A.this) - override def canEqual(that: scala.Any): scala.Boolean = that.isInstanceOf[A[T] @scala.unchecked] + override def canEqual(that: scala.Any): scala.Boolean = that.isInstanceOf[A[A.this.T] @scala.unchecked] override def productArity: scala.Int = 1 override def productPrefix: java.lang.String = "A" override def productElement(n: scala.Int): scala.Any = n match { @@ -21,4 +21,4 @@ case class A[T](x: T) { throw new java.lang.IndexOutOfBoundsException(n.toString()) } } -object A extends scala.AnyRef +object A extends scala.AnyRef \ No newline at end of file diff --git a/tests/run/tasty-macro-positions.check b/tests/run/tasty-macro-positions.check index 25b2142bc36f..23c6a5fa3ffe 100644 --- a/tests/run/tasty-macro-positions.check +++ b/tests/run/tasty-macro-positions.check @@ -11,7 +11,7 @@ quoted_2.scala:[217..222] quoted_2.scala:[232..245] ("abc": scala.Predef.String) quoted_2.scala:[255..269] -_root_.scala.StringContext.apply(("abc", "": scala.[scala#Predef.String])).s(("def": scala.[scala.Any])) +_root_.scala.StringContext.apply(("abc", "": scala.[scala.Predef.String])).s(("def": scala.[scala.Any])) quoted_2.scala:[281..282] a quoted_2.scala:[293..294] @@ -25,7 +25,7 @@ quoted_2.scala:[329..334] quoted_2.scala:[345..358] ("abc": scala.Predef.String) quoted_2.scala:[369..383] -_root_.scala.StringContext.apply(("abc", "": scala.[scala#Predef.String])).s(("def": scala.[scala.Any])) +_root_.scala.StringContext.apply(("abc", "": scala.[scala.Predef.String])).s(("def": scala.[scala.Any])) quoted_2.scala:[426..427] T quoted_2.scala:[438..444] diff --git a/tests/run/type-show/Macro_1.scala b/tests/run/type-show/Macro_1.scala new file mode 100644 index 000000000000..87dbe60603c1 --- /dev/null +++ b/tests/run/type-show/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted._ +import scala.tasty._ + +object TypeToolbox { + inline def show[A]: String = ${ showImpl('[A]) } + private def showImpl[A, B](a: Type[A])(implicit refl: Reflection): Expr[String] = { + import refl._ + import scala.quoted.Toolbox.Default._ + a.show.toExpr + } +} diff --git a/tests/run/type-show/Test_2.scala b/tests/run/type-show/Test_2.scala new file mode 100644 index 000000000000..471d19840338 --- /dev/null +++ b/tests/run/type-show/Test_2.scala @@ -0,0 +1,17 @@ + +object Test { + import TypeToolbox._ + def main(args: Array[String]): Unit = { + val x = 5 + assert(show[x.type] == "x.type") + assert(show[Nil.type] == "scala.Nil.type") + assert(show[Int] == "scala.Int") + assert(show[Int => Int] == "scala.Function1[scala.Int, scala.Int]") + assert(show[(Int, String)] == "scala.Tuple2[scala.Int, scala.Predef.String]") + + // TODO: more complex types: + // - implicit function types + // - dependent function types + // - refinement types + } +}