From 8f21d244a6aea4a89940c74e9fbe73a6a68e9813 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 18 Sep 2020 16:50:26 +0200 Subject: [PATCH 1/3] fix #9873: move scala.Enum to scala.reflect.Enum scala.reflect.Enum is now a universal super trait. Also avoid using derivesFrom(defn.EnumClass) and instead look for enum flag. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 5 +-- .../dotty/tools/dotc/ast/DesugarEnums.scala | 4 +-- .../dotty/tools/dotc/core/Definitions.scala | 2 +- .../dotty/tools/dotc/transform/SymUtils.scala | 3 ++ .../dotc/transform/SyntheticMembers.scala | 3 +- .../src/dotty/tools/dotc/typer/Checking.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 5 +-- library/src-bootstrapped/scala/Enum.scala | 9 ++--- .../src-bootstrapped/scala/reflect/Enum.scala | 10 ++++++ library/src-non-bootstrapped/scala/Enum.scala | 3 ++ .../runtime/EnumValueSerializationProxy.java | 36 ------------------- .../scala/runtime/EnumValues.scala | 21 ----------- tests/neg/enumsLabel-overrides.scala | 15 ++++++++ tests/neg/enumsLabel-singleimpl.scala | 15 ++++++++ tests/neg/enumsLabelDef.scala | 22 ------------ tests/neg/supertraits.scala | 11 ++++++ tests/patmat/i7186.scala | 6 ++-- tests/pos/enum-List-control.scala | 2 +- tests/run/generic/Enum.scala | 2 +- 19 files changed, 76 insertions(+), 100 deletions(-) create mode 100644 library/src-bootstrapped/scala/reflect/Enum.scala delete mode 100644 library/src-non-bootstrapped/scala/runtime/EnumValueSerializationProxy.java delete mode 100644 library/src-non-bootstrapped/scala/runtime/EnumValues.scala create mode 100644 tests/neg/enumsLabel-overrides.scala create mode 100644 tests/neg/enumsLabel-singleimpl.scala delete mode 100644 tests/neg/enumsLabelDef.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index ad8876e4f67b..f1793071b0fa 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -403,6 +403,7 @@ object desugar { val isCaseObject = mods.is(Case) && isObject val isEnum = mods.isEnumClass && !mods.is(Module) def isEnumCase = mods.isEnumCase + def isNonEnumCase = !isEnumCase && (isCaseClass || isCaseObject) val isValueClass = parents.nonEmpty && isAnyVal(parents.head) // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. @@ -621,10 +622,10 @@ object desugar { var parents1 = parents if (isEnumCase && parents.isEmpty) parents1 = enumClassTypeRef :: Nil - if (isCaseClass | isCaseObject) + if (isNonEnumCase) parents1 = parents1 :+ scalaDot(str.Product.toTypeName) :+ scalaDot(nme.Serializable.toTypeName) if (isEnum) - parents1 = parents1 :+ ref(defn.EnumClass.typeRef) + parents1 = parents1 :+ ref(defn.EnumClass) // derived type classes of non-module classes go to their companions val (clsDerived, companionDerived) = diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 56aa41c00cc7..67ab44c639e9 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -282,10 +282,10 @@ object DesugarEnums { private def isJavaEnum(using Context): Boolean = enumClass.derivesFrom(defn.JavaEnumClass) def ordinalMeth(body: Tree)(using Context): DefDef = - DefDef(nme.ordinal, Nil, Nil, TypeTree(defn.IntType), body) + DefDef(nme.ordinal, Nil, Nil, TypeTree(defn.IntType), body).withAddedFlags(Synthetic) def enumLabelMeth(body: Tree)(using Context): DefDef = - DefDef(nme.enumLabel, Nil, Nil, TypeTree(defn.StringType), body) + DefDef(nme.enumLabel, Nil, Nil, TypeTree(defn.StringType), body).withAddedFlags(Synthetic) def ordinalMethLit(ord: Int)(using Context): DefDef = ordinalMeth(Literal(Constant(ord))) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index fad306a7759f..497c0115f6b5 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -754,7 +754,7 @@ class Definitions { @tu lazy val SomeClass: ClassSymbol = requiredClass("scala.Some") @tu lazy val NoneModule: Symbol = requiredModule("scala.None") - @tu lazy val EnumClass: ClassSymbol = requiredClass("scala.Enum") + @tu lazy val EnumClass: ClassSymbol = requiredClass("scala.reflect.Enum") @tu lazy val EnumValueSerializationProxyClass: ClassSymbol = requiredClass("scala.runtime.EnumValueSerializationProxy") @tu lazy val EnumValueSerializationProxyConstructor: TermSymbol = diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 133a7b9c4004..9089ca580307 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -171,6 +171,9 @@ object SymUtils { self } + def isEnum(using Context): Boolean = self.is(Enum, butNot=JavaDefined) + def isEnumClass(using Context): Boolean = isEnum && !self.is(Case) + /** Does this symbol refer to anonymous classes synthesized by enum desugaring? */ def isEnumAnonymClass(using Context): Boolean = self.isAnonymousClass && (self.owner.name.eq(nme.DOLLAR_NEW) || self.owner.is(CaseVal)) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index bbd2f13395a0..982ba4c00b22 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -94,8 +94,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { lazy val accessors = if (isDerivedValueClass(clazz)) clazz.paramAccessors.take(1) // Tail parameters can only be `erased` else clazz.caseAccessors - val isEnumCase = clazz.derivesFrom(defn.EnumClass) && clazz != defn.EnumClass - val isEnumValue = isEnumCase && clazz.isAnonymousClass && clazz.classParents.head.classSymbol.is(Enum) + val isEnumValue = clazz.isAnonymousClass && clazz.classParents.head.classSymbol.is(Enum) val isNonJavaEnumValue = isEnumValue && !clazz.derivesFrom(defn.JavaEnumClass) val symbolsToSynthesize: List[Symbol] = diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 6d4bb91b8621..1bd4f6c3ca82 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1094,7 +1094,7 @@ trait Checking { // Since enums are classes and Namer checks that classes don't extend multiple classes, we only check the class // parent. // - // Unlike firstParent.derivesFrom(defn.EnumClass), this test allows inheriting from `Enum` by hand; + // this test allows inheriting from `Enum` by hand; // see enum-List-control.scala. report.error(ClassCannotExtendEnum(cls, firstParent), cdef.srcPos) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bb92346812bf..3df4d89eaf7b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2106,7 +2106,8 @@ class Typer extends Namer val constr1 = typed(constr).asInstanceOf[DefDef] val parentsWithClass = ensureFirstTreeIsClass(parents.mapconserve(typedParent).filterConserve(!_.isEmpty), cdef.nameSpan) val parents1 = ensureConstrCall(cls, parentsWithClass)(using superCtx) - val firstParent = parents1.head.tpe.dealias.typeSymbol + val firstParentTpe = parents1.head.tpe.dealias + val firstParent = firstParentTpe.typeSymbol checkEnumParent(cls, firstParent) @@ -2123,7 +2124,7 @@ class Typer extends Namer .withType(dummy.termRef) if (!cls.isOneOf(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls, cdef.sourcePos.withSpan(cdef.nameSpan)) - if cls.derivesFrom(defn.EnumClass) then + if cls.isEnum || firstParentTpe.classSymbol.isEnum then checkEnum(cdef, cls, firstParent) val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls) diff --git a/library/src-bootstrapped/scala/Enum.scala b/library/src-bootstrapped/scala/Enum.scala index ff713f90fe11..39b4bf1da280 100644 --- a/library/src-bootstrapped/scala/Enum.scala +++ b/library/src-bootstrapped/scala/Enum.scala @@ -1,10 +1,5 @@ package scala /** A base trait of all enum classes */ -trait Enum extends Product, Serializable: - - /** A string uniquely identifying a case of an enum */ - def enumLabel: String - - /** A number uniquely identifying a case of an enum */ - def ordinal: Int +@deprecated("scala.Enum has moved to scala.reflect.Enum", "3.0.0-M1") +type Enum = scala.reflect.Enum diff --git a/library/src-bootstrapped/scala/reflect/Enum.scala b/library/src-bootstrapped/scala/reflect/Enum.scala new file mode 100644 index 000000000000..decfe28a7632 --- /dev/null +++ b/library/src-bootstrapped/scala/reflect/Enum.scala @@ -0,0 +1,10 @@ +package scala.reflect + +/** A base trait of all enum classes */ +super trait Enum extends Any, Product, Serializable: + + /** A string uniquely identifying a case of an enum */ + def enumLabel: String + + /** A number uniquely identifying a case of an enum */ + def ordinal: Int diff --git a/library/src-non-bootstrapped/scala/Enum.scala b/library/src-non-bootstrapped/scala/Enum.scala index ce21eb12cd08..ff713f90fe11 100644 --- a/library/src-non-bootstrapped/scala/Enum.scala +++ b/library/src-non-bootstrapped/scala/Enum.scala @@ -3,5 +3,8 @@ package scala /** A base trait of all enum classes */ trait Enum extends Product, Serializable: + /** A string uniquely identifying a case of an enum */ + def enumLabel: String + /** A number uniquely identifying a case of an enum */ def ordinal: Int diff --git a/library/src-non-bootstrapped/scala/runtime/EnumValueSerializationProxy.java b/library/src-non-bootstrapped/scala/runtime/EnumValueSerializationProxy.java deleted file mode 100644 index 206a48f2b770..000000000000 --- a/library/src-non-bootstrapped/scala/runtime/EnumValueSerializationProxy.java +++ /dev/null @@ -1,36 +0,0 @@ -package scala.runtime; - -import java.io.Serializable; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; - -/** A serialization proxy for singleton enum values, based on `scala.runtime.ModuleSerializationProxy` */ -public final class EnumValueSerializationProxy implements Serializable { - private static final long serialVersionUID = 1L; - private final Class enumClass; - private final int ordinal; - private static final ClassValue enumValues = new ClassValue() { - @Override - protected Object[] computeValue(Class type) { - try { - return AccessController.doPrivileged((PrivilegedExceptionAction) () -> - (Object[])type.getMethod("values").invoke(null)); - } catch (PrivilegedActionException e) { - Throwable cause = e.getCause(); - if (cause instanceof RuntimeException) throw (RuntimeException) cause; - else throw new RuntimeException(cause); - } - } - }; - - public EnumValueSerializationProxy(Class enumClass, int ordinal) { - this.enumClass = enumClass; - this.ordinal = ordinal; - } - - @SuppressWarnings("unused") - private Object readResolve() { - return enumValues.get(enumClass)[ordinal]; - } -} diff --git a/library/src-non-bootstrapped/scala/runtime/EnumValues.scala b/library/src-non-bootstrapped/scala/runtime/EnumValues.scala deleted file mode 100644 index b3f960b7f604..000000000000 --- a/library/src-non-bootstrapped/scala/runtime/EnumValues.scala +++ /dev/null @@ -1,21 +0,0 @@ -package scala.runtime - -import scala.collection.immutable.TreeMap - -class EnumValues[E <: Enum] { - private[this] var myMap: Map[Int, E] = TreeMap.empty - private[this] var fromNameCache: Map[String, E] = null - - def register(v: E) = { - require(!myMap.contains(v.ordinal)) - myMap = myMap.updated(v.ordinal, v) - fromNameCache = null - } - - def fromInt: Map[Int, E] = myMap - def fromName: Map[String, E] = { - if (fromNameCache == null) fromNameCache = myMap.values.map(v => v.toString -> v).toMap - fromNameCache - } - def values: Iterable[E] = myMap.values -} diff --git a/tests/neg/enumsLabel-overrides.scala b/tests/neg/enumsLabel-overrides.scala new file mode 100644 index 000000000000..4b7036b4785f --- /dev/null +++ b/tests/neg/enumsLabel-overrides.scala @@ -0,0 +1,15 @@ +trait Mixin { def enumLabel: String = "mixin" } + +enum Mixed extends Mixin { + case B // error: overriding method enumLabel in trait Mixin of type => String; +} + +enum MixedAlso { + case C extends MixedAlso with Mixin // error: overriding method enumLabel in trait Mixin of type => String; +} + +trait HasEnumLabel { def enumLabel: String } + +enum MyEnum extends HasEnumLabel { + case D // ok +} diff --git a/tests/neg/enumsLabel-singleimpl.scala b/tests/neg/enumsLabel-singleimpl.scala new file mode 100644 index 000000000000..225519d5dcd9 --- /dev/null +++ b/tests/neg/enumsLabel-singleimpl.scala @@ -0,0 +1,15 @@ +enum Labelled { + + case A // error: method enumLabel of type => String needs `override` modifier + + def enumLabel: String = "nolabel" + +} + +enum Ordinalled { + + case A // error: method ordinal of type => Int needs `override` modifier + + def ordinal: Int = -1 + +} diff --git a/tests/neg/enumsLabelDef.scala b/tests/neg/enumsLabelDef.scala deleted file mode 100644 index e7bc10108bb6..000000000000 --- a/tests/neg/enumsLabelDef.scala +++ /dev/null @@ -1,22 +0,0 @@ -enum Labelled { - - case A // error overriding method enumLabel in class Labelled of type => String; - - def enumLabel: String = "nolabel" -} - -trait Mixin { def enumLabel: String = "mixin" } - -enum Mixed extends Mixin { - case B // error overriding method enumLabel in trait Mixin of type => String; -} - -enum MixedAlso { - case C extends MixedAlso with Mixin // error overriding method enumLabel in trait Mixin of type => String; -} - -trait HasEnumLabel { def enumLabel: String } - -enum MyEnum extends HasEnumLabel { - case D // ok -} diff --git a/tests/neg/supertraits.scala b/tests/neg/supertraits.scala index 1aed4f4f18a4..61a837ff47d1 100644 --- a/tests/neg/supertraits.scala +++ b/tests/neg/supertraits.scala @@ -11,3 +11,14 @@ case object b val y = if ??? then a else b val y1: Product = y // error val y2: Serializable = y // error + +enum Color { + case Red, Green, Blue +} + +enum Nucleobase { + case A, C, G, T +} + +val z = if ??? then Color.Red else Nucleobase.G +val z1: reflect.Enum = z // error: Found: (z : Object) Required: reflect.Enum diff --git a/tests/patmat/i7186.scala b/tests/patmat/i7186.scala index 95c7d4db02c4..d34945bed756 100644 --- a/tests/patmat/i7186.scala +++ b/tests/patmat/i7186.scala @@ -1,5 +1,7 @@ import MIPS._ +import deriving.Mirror.SumOf + object MIPS { type Labels = Label | ControlLabel type Src = Register | Constant @@ -233,8 +235,8 @@ object printMips { def getScopedLabel(s: Scoped): String = "L" + getScopedId(s) - def printEnum[E](e: String => Enum, t: E, code: String) = { - val num = e(t.toString).ordinal + def printEnum[E: SumOf](e: String => E, t: E, code: String) = { + val num = summon[SumOf[E]].ordinal(e(t.toString)) s"$code$num" } } diff --git a/tests/pos/enum-List-control.scala b/tests/pos/enum-List-control.scala index 8fce310eb141..99fce35c032c 100644 --- a/tests/pos/enum-List-control.scala +++ b/tests/pos/enum-List-control.scala @@ -1,4 +1,4 @@ -abstract sealed class List[T] extends Enum +abstract sealed class List[T] extends reflect.Enum object List { final class Cons[T](x: T, xs: List[T]) extends List[T] { def ordinal = 0 diff --git a/tests/run/generic/Enum.scala b/tests/run/generic/Enum.scala index 3e04786de07d..40ce2983730b 100644 --- a/tests/run/generic/Enum.scala +++ b/tests/run/generic/Enum.scala @@ -22,4 +22,4 @@ object runtime { } def values: Iterable[E] = myMap.values } -} \ No newline at end of file +} From c76c4ee911537a4153c0753aaa773f5287307ac5 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 5 Oct 2020 14:50:28 +0200 Subject: [PATCH 2/3] adjust formatting, use isEnumClass, adjust doc --- .../src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala | 4 +++- compiler/src/dotty/tools/dotc/transform/SymUtils.scala | 2 +- library/src-bootstrapped/scala/reflect/Enum.scala | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala index 976b26379198..92e12229d508 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala @@ -17,6 +17,8 @@ import scala.jdk.CollectionConverters._ import collection.mutable import java.nio.file.Paths +import dotty.tools.dotc.transform.SymUtils._ + import PartialFunction.condOpt import ast.untpd.{given _} @@ -191,7 +193,7 @@ class ExtractSemanticDB extends Phase: val selfSpan = tree.self.span if selfSpan.exists && selfSpan.hasLength then traverse(tree.self) - if tree.symbol.owner.is(Enum, butNot=Case) then + if tree.symbol.owner.isEnumClass then tree.body.foreachUntilImport(traverse).foreach(traverse) // the first import statement else tree.body.foreach(traverse) diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 9089ca580307..e70f720501fe 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -171,7 +171,7 @@ object SymUtils { self } - def isEnum(using Context): Boolean = self.is(Enum, butNot=JavaDefined) + def isEnum(using Context): Boolean = self.is(Enum, butNot = JavaDefined) def isEnumClass(using Context): Boolean = isEnum && !self.is(Case) /** Does this symbol refer to anonymous classes synthesized by enum desugaring? */ diff --git a/library/src-bootstrapped/scala/reflect/Enum.scala b/library/src-bootstrapped/scala/reflect/Enum.scala index decfe28a7632..8661e419f1ee 100644 --- a/library/src-bootstrapped/scala/reflect/Enum.scala +++ b/library/src-bootstrapped/scala/reflect/Enum.scala @@ -1,6 +1,6 @@ package scala.reflect -/** A base trait of all enum classes */ +/** A base trait of all Scala enum definitions */ super trait Enum extends Any, Product, Serializable: /** A string uniquely identifying a case of an enum */ From 059159757171f946076293513156a37b733f7805 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 5 Oct 2020 17:15:07 +0200 Subject: [PATCH 3/3] update docs --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- docs/docs/reference/enums/enums.md | 8 ++++---- library/src-bootstrapped/scala/Enum.scala | 2 +- tastydoc/test/dotty/tastydoc/Tests.scala | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 1bd4f6c3ca82..fdad1137ebaa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1073,7 +1073,7 @@ trait Checking { if !Inliner.inInlineMethod && !ctx.isInlineContext then report.error(em"$what can only be used in an inline method", pos) - /** 1. Check that all case classes that extend `scala.Enum` are `enum` cases + /** 1. Check that all case classes that extend `scala.reflect.Enum` are `enum` cases * 2. Check that parameterised `enum` cases do not extend java.lang.Enum. * 3. Check that only a static `enum` base class can extend java.lang.Enum. */ diff --git a/docs/docs/reference/enums/enums.md b/docs/docs/reference/enums/enums.md index 86350110b03e..0553ac6d9e31 100644 --- a/docs/docs/reference/enums/enums.md +++ b/docs/docs/reference/enums/enums.md @@ -109,14 +109,14 @@ For a more in-depth example of using Scala 3 enums from Java, see [this test](ht ### Implementation -Enums are represented as `sealed` classes that extend the `scala.Enum` trait. +Enums are represented as `sealed` classes that extend the `scala.reflect.Enum` trait. This trait defines two public methods, `ordinal` and `enumLabel`: ```scala -package scala +package scala.reflect -/** A base trait of all enum classes */ -trait Enum extends Product with Serializable { +/** A base trait of all Scala enum definitions */ +super trait Enum extends Any with Product with Serializable { /** A string uniquely identifying a case of an enum */ def enumLabel: String diff --git a/library/src-bootstrapped/scala/Enum.scala b/library/src-bootstrapped/scala/Enum.scala index 39b4bf1da280..d1143e042346 100644 --- a/library/src-bootstrapped/scala/Enum.scala +++ b/library/src-bootstrapped/scala/Enum.scala @@ -1,5 +1,5 @@ package scala -/** A base trait of all enum classes */ +/** A base trait of all Scala enum definitions */ @deprecated("scala.Enum has moved to scala.reflect.Enum", "3.0.0-M1") type Enum = scala.reflect.Enum diff --git a/tastydoc/test/dotty/tastydoc/Tests.scala b/tastydoc/test/dotty/tastydoc/Tests.scala index e05499ee1547..b3e8f848441e 100644 --- a/tastydoc/test/dotty/tastydoc/Tests.scala +++ b/tastydoc/test/dotty/tastydoc/Tests.scala @@ -55,7 +55,6 @@ class Tests { //Individual files "scala.\\$times\\$colon*", "scala.Conversion*", - "scala.Enum*", "scala.Eql*", "scala.forceInline*", "scala.FunctionXXL*",