From 556163474c3a237ee8e69dd3096476e6c5927c8e Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Nov 2024 18:04:04 +0100 Subject: [PATCH 01/15] A strawman for aggregate literals A test case that shows that we can have an "inline type class" that allows to use a typeclass-based scheme for sequence literals where instances can be created with macros. --- tests/pos/seqlits.check | 6 ++++++ tests/pos/seqlits.scala | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 tests/pos/seqlits.check create mode 100644 tests/pos/seqlits.scala diff --git a/tests/pos/seqlits.check b/tests/pos/seqlits.check new file mode 100644 index 000000000000..fb56d52167dc --- /dev/null +++ b/tests/pos/seqlits.check @@ -0,0 +1,6 @@ +last was evaluated +last was evaluated +Seq List(1, 2, 3, 4) +Vector Vector(1, 2, 3, 4) +last was evaluated +Task with elems List(1, 2, 3, 4) diff --git a/tests/pos/seqlits.scala b/tests/pos/seqlits.scala new file mode 100644 index 000000000000..3b7775994a07 --- /dev/null +++ b/tests/pos/seqlits.scala @@ -0,0 +1,41 @@ +import reflect.ClassTag + +/** Some delayed computation like a Mill Task */ +case class Task[T](body: () => T) + +object SeqLits: + + /** A typeclass to map sequence literals with `T` elements + * to some collection type `C`. + */ + trait FromArray[T, +C]: + inline def fromArray(inline xs: IArray[T]): C + + /** An emulation of a sequence literal like [1, 2, 3]. Since we don't have that + * syntax yet, we express it here as `seqLit(1, 2, 3)`. + */ + inline def seqLit[T: ClassTag, C](inline xs: T*)(using inline f: FromArray[T, C]): C = + f.fromArray(IArray(xs*)) + + /** Straightfoward mapping to Seq */ + given [T] => FromArray[T, Seq[T]]: + inline def fromArray(inline xs: IArray[T]) = Seq(xs*) + + /** A more specific mapping to Vector */ + given [T] => FromArray[T, Vector[T]]: + inline def fromArray(inline xs: IArray[T]) = Vector(xs*) + + /** A delaying mapping to Task */ + given [T] => FromArray[T, Task[Seq[T]]]: + inline def fromArray(inline xs: IArray[T]) = Task(() => Seq(xs*)) + + def last: Int = { println("last was evaluated"); 4 } + + val s: Seq[Int] = seqLit(1, 2, 3, last) + val v: Vector[Int] = seqLit(1, 2, 3, last) + val t: Task[Seq[Int]] = seqLit(1, 2, 3, last) + + @main def Test = + println(s"Seq $s") + println(s"Vector $v") + println(s"${t.getClass.getSimpleName} with elems ${t.body()}") From 5dbf2bf7a22ac0c866209f21079b297a1bfba9bc Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Nov 2024 19:42:08 +0100 Subject: [PATCH 02/15] Prototype InlineConversion as well. Also: Move test to run --- tests/pos/inline-conversion.scala | 11 +++++++++++ tests/pos/seqlits.check | 6 ------ tests/run/seqlits.check | 12 ++++++++++++ tests/{pos => run}/seqlits.scala | 17 +++++++++++++---- 4 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 tests/pos/inline-conversion.scala delete mode 100644 tests/pos/seqlits.check create mode 100644 tests/run/seqlits.check rename tests/{pos => run}/seqlits.scala (68%) diff --git a/tests/pos/inline-conversion.scala b/tests/pos/inline-conversion.scala new file mode 100644 index 000000000000..e22539bc80db --- /dev/null +++ b/tests/pos/inline-conversion.scala @@ -0,0 +1,11 @@ +abstract class InlineConversion[-From, +To]: + inline def applyInline(inline x: From): To + extension (x: From) inline def convertInline: To = applyInline(x) + +abstract class Conversion[-From, +To] extends InlineConversion[From, To]: + def apply(x: From): To + + inline def applyInline(inline x: From): To = apply(x) + + extension (x: From) def convert: To = apply(x) + diff --git a/tests/pos/seqlits.check b/tests/pos/seqlits.check deleted file mode 100644 index fb56d52167dc..000000000000 --- a/tests/pos/seqlits.check +++ /dev/null @@ -1,6 +0,0 @@ -last was evaluated -last was evaluated -Seq List(1, 2, 3, 4) -Vector Vector(1, 2, 3, 4) -last was evaluated -Task with elems List(1, 2, 3, 4) diff --git a/tests/run/seqlits.check b/tests/run/seqlits.check new file mode 100644 index 000000000000..cbf9734fe4df --- /dev/null +++ b/tests/run/seqlits.check @@ -0,0 +1,12 @@ +last was evaluated +last was evaluated +Seq Vector(1, 2, 3, 4) +Vector Vector(1, 2, 3, 4) +last was evaluated +Task with elems List(1, 2, 3, 4) +last was evaluated +last was evaluated +Seq Vector(1, 2, 3, 4) +Vector Vector(1, 2, 3, 4) +last was evaluated +Task with elems List(1, 2, 3, 4) diff --git a/tests/pos/seqlits.scala b/tests/run/seqlits.scala similarity index 68% rename from tests/pos/seqlits.scala rename to tests/run/seqlits.scala index 3b7775994a07..b341f529a378 100644 --- a/tests/pos/seqlits.scala +++ b/tests/run/seqlits.scala @@ -17,6 +17,9 @@ object SeqLits: inline def seqLit[T: ClassTag, C](inline xs: T*)(using inline f: FromArray[T, C]): C = f.fromArray(IArray(xs*)) + inline def seqLit2[T, C](inline xs: IArray[T])(using inline f: FromArray[T, C]): C = + f.fromArray(xs) + /** Straightfoward mapping to Seq */ given [T] => FromArray[T, Seq[T]]: inline def fromArray(inline xs: IArray[T]) = Seq(xs*) @@ -31,11 +34,17 @@ object SeqLits: def last: Int = { println("last was evaluated"); 4 } - val s: Seq[Int] = seqLit(1, 2, 3, last) - val v: Vector[Int] = seqLit(1, 2, 3, last) - val t: Task[Seq[Int]] = seqLit(1, 2, 3, last) - @main def Test = + val s: Seq[Int] = seqLit(1, 2, 3, last) + val v: Vector[Int] = seqLit(1, 2, 3, last) + val t: Task[Seq[Int]] = seqLit(1, 2, 3, last) println(s"Seq $s") println(s"Vector $v") println(s"${t.getClass.getSimpleName} with elems ${t.body()}") + + val s2: Seq[Int] = seqLit2(IArray(1, 2, 3, last)) + val v2: Vector[Int] = seqLit2(IArray(1, 2, 3, last)) + val t2: Task[Seq[Int]] = seqLit2(IArray(1, 2, 3, last)) + println(s"Seq $s2") + println(s"Vector $v2") + println(s"${t2.getClass.getSimpleName} with elems ${t2.body()}") From bf79ce2675f1aa95e83d75118643e23681d47d9f Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 24 Nov 2024 12:25:19 +0100 Subject: [PATCH 03/15] Add assert to typedSeqLiteral --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 76b853c4aabd..373243174b59 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2410,6 +2410,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer assign(elems1, elemtpt1) } else { + assert(tree.isInstanceOf[Trees.JavaSeqLiteral[?]]) val elems1 = tree.elems.mapconserve(typed(_, elemProto)) val elemtptType = if (isFullyDefined(elemProto, ForceDegree.none)) From 6945e1a68948c7b679e0fe2a058bebed64714042 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 25 Nov 2024 11:19:09 +0100 Subject: [PATCH 04/15] Implement collection literals --- .../dotty/tools/dotc/core/Definitions.scala | 5 +- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 12 ++- .../dotty/tools/dotc/transform/Erasure.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 4 +- .../dotty/tools/dotc/typer/Inferencing.scala | 10 ++- .../src/dotty/tools/dotc/typer/Typer.scala | 71 +++++++++++++++--- docs/_docs/internals/syntax.md | 2 + .../ExpressibleAsCollectionLiteral.scala | 56 ++++++++++++++ tests/neg/seqlits.check | 11 +++ tests/neg/seqlits.scala | 21 ++++++ tests/run/seqlits.check | 18 +++-- tests/run/seqlits.scala | 74 +++++++++---------- 14 files changed, 228 insertions(+), 61 deletions(-) create mode 100644 library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala create mode 100644 tests/neg/seqlits.check create mode 100644 tests/neg/seqlits.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2890bdf306be..80c5d1d033f1 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -563,7 +563,7 @@ class Definitions { @tu lazy val Seq_lengthCompare: Symbol = SeqClass.requiredMethod(nme.lengthCompare, List(IntType)) @tu lazy val Seq_length : Symbol = SeqClass.requiredMethod(nme.length) @tu lazy val Seq_toSeq : Symbol = SeqClass.requiredMethod(nme.toSeq) - + @tu lazy val MapModule: Symbol = requiredModule("scala.collection.immutable.Map") @tu lazy val StringOps: Symbol = requiredClass("scala.collection.StringOps") @tu lazy val StringOps_format: Symbol = StringOps.requiredMethod(nme.format) @@ -582,6 +582,9 @@ class Definitions { @tu lazy val IArrayModule: Symbol = requiredModule("scala.IArray") def IArrayModuleClass: Symbol = IArrayModule.moduleClass + @tu lazy val ExpressibleAsCollectionLiteralClass: ClassSymbol = requiredClass("scala.compiletime.ExpressibleAsCollectionLiteral") + @tu lazy val ExpressibleACL_fromLiteral: Symbol = ExpressibleAsCollectionLiteralClass.requiredMethod("fromLiteral") + @tu lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", java.lang.Void.TYPE, UnitEnc, nme.specializedTypeNames.Void) def UnitClass(using Context): ClassSymbol = UnitType.symbol.asClass def UnitModuleClass(using Context): Symbol = UnitType.symbol.asClass.linkedClass diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 56d71c7fb57e..7ed6c76680bc 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -500,6 +500,7 @@ object StdNames { val foreach: N = "foreach" val format: N = "format" val fromDigits: N = "fromDigits" + val fromLiteral: N = "fromLiteral" val fromProduct: N = "fromProduct" val genericArrayOps: N = "genericArrayOps" val genericClass: N = "genericClass" diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c5937074f4bc..ea4dc80730d6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4913,7 +4913,7 @@ object Types extends TypeUtils { /** The state owning the variable. This is at first `creatorState`, but it can * be changed to an enclosing state on a commit. */ - private[core] var owningState: WeakReference[TyperState] | Null = + private[dotc] var owningState: WeakReference[TyperState] | Null = if (creatorState == null) null else new WeakReference(creatorState) /** The nesting level of this type variable in the current typer state. This is usually diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7933cbbea12f..0f4f93073e73 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2390,7 +2390,7 @@ object Parsers { in.token match case IMPLICIT => closure(start, location, modifiers(BitSet(IMPLICIT))) - case LBRACKET => + case LBRACKET if followingIsArrow() => val start = in.offset val tparams = typeParamClause(ParamOwner.Type) val arrowOffset = accept(ARROW) @@ -2710,6 +2710,7 @@ object Parsers { * | xmlLiteral * | SimpleRef * | `(` [ExprsInParens] `)` + * | `[` ExprsInBrackets `]` * | SimpleExpr `.` id * | SimpleExpr `.` MatchClause * | SimpleExpr (TypeArgs | NamedTypeArgs) @@ -2745,6 +2746,9 @@ object Parsers { case LBRACE | INDENT => canApply = false blockExpr() + case LBRACKET => + inBrackets: + SeqLiteral(exprsInBrackets(), TypeTree()) case QUOTE => quote(location.inPattern) case NEW => @@ -2840,6 +2844,12 @@ object Parsers { commaSeparatedRest(exprOrBinding(), exprOrBinding) } + /** ExprsInBrackets ::= ExprInParens {`,' ExprInParens} */ + def exprsInBrackets(): List[Tree] = + if in.token == RBRACKET then Nil + else in.currentRegion.withCommasExpected: + commaSeparatedRest(exprInParens(), exprInParens) + /** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)' * | `(' [ExprsInParens `,'] PostfixExpr `*' ')' */ diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 7414ca7e69c6..e39524914ddb 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -891,7 +891,7 @@ object Erasure { // The following four methods take as the proto-type the erasure of the pre-existing type, // if the original proto-type is not a value type. // This makes all branches be adapted to the correct type. - override def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): SeqLiteral = + override def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): Tree = super.typedSeqLiteral(tree, erasure(tree.typeOpt)) // proto type of typed seq literal is original type; diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 193cc443b4ae..38a80abcc571 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -443,10 +443,12 @@ object Implicits: extends SearchResult with RefAndLevel with Showable: final def found = ref :: Nil + def isAmbiguousGiven(tree: Tree) = tree.tpe.isInstanceOf[AmbiguousImplicits | TooUnspecific] + /** A failed search */ case class SearchFailure(tree: Tree) extends SearchResult { require(tree.tpe.isInstanceOf[SearchFailureType], s"unexpected type for ${tree}") - final def isAmbiguous: Boolean = tree.tpe.isInstanceOf[AmbiguousImplicits | TooUnspecific] + final def isAmbiguous: Boolean = isAmbiguousGiven(tree) final def reason: SearchFailureType = tree.tpe.asInstanceOf[SearchFailureType] final def found = tree.tpe match case tpe: AmbiguousImplicits => tpe.alt1.ref :: tpe.alt2.ref :: Nil diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 2ebcd96d5bde..c0be3b58bd49 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -58,11 +58,13 @@ object Inferencing { * The method is called to instantiate type variables before an implicit search. */ def instantiateSelected(tp: Type, tvars: List[Type])(using Context): Unit = - if (tvars.nonEmpty) - IsFullyDefinedAccumulator( + if tvars.nonEmpty then instantiateSelected(tp, tvars.contains, minimize = true) + + def instantiateSelected(tp: Type, cond: TypeVar => Boolean, minimize: Boolean)(using Context): Unit = + IsFullyDefinedAccumulator( new ForceDegree.Value(IfBottom.flip): - override def appliesTo(tvar: TypeVar) = tvars.contains(tvar), - minimizeSelected = true + override def appliesTo(tvar: TypeVar) = cond(tvar), + minimizeSelected = minimize ).process(tp) /** Instantiate any type variables in `tp` whose bounds contain a reference to diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 373243174b59..ce0f47c61d99 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2394,7 +2394,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer Annotation(defn.RequiresCapabilityAnnot, cap, tree.span)))) res.withNotNullInfo(expr1.notNullInfo.terminatedInfo) - def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): SeqLiteral = { + def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): Tree = val elemProto = pt.stripNull().elemType match { case NoType => WildcardType case bounds: TypeBounds => WildcardType(bounds) @@ -2404,25 +2404,78 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def assign(elems1: List[Tree], elemtpt1: Tree) = assignType(cpy.SeqLiteral(tree)(elems1, elemtpt1), elems1, elemtpt1) - if (!tree.elemtpt.isEmpty) { + // Seq literal used in varargs: elem type is given + def varargSeqLiteral = val elemtpt1 = typed(tree.elemtpt, elemProto) val elems1 = tree.elems.mapconserve(typed(_, elemtpt1.tpe)) assign(elems1, elemtpt1) - } - else { - assert(tree.isInstanceOf[Trees.JavaSeqLiteral[?]]) + + // Seq literal used in Java annotations: elem type needs to be computed + def javaSeqLiteral = val elems1 = tree.elems.mapconserve(typed(_, elemProto)) val elemtptType = - if (isFullyDefined(elemProto, ForceDegree.none)) + if isFullyDefined(elemProto, ForceDegree.none) then elemProto - else if (tree.elems.isEmpty && tree.isInstanceOf[Trees.JavaSeqLiteral[?]]) + else if tree.elems.isEmpty then defn.ObjectType // generic empty Java varargs are of type Object[] else TypeComparer.lub(elems1.tpes) val elemtpt1 = typed(tree.elemtpt, elemtptType) assign(elems1, elemtpt1) - } - } + + // Stand-alone collection literal [x1, ..., xN] + def collectionLiteral = + def isArrow(tree: untpd.Tree) = tree match + case untpd.InfixOp(_, Ident(nme.PUREARROW), _) => true + case _ => false + + // The default maker if no typeclass is searched or found + def defaultMaker = + if tree.elems.nonEmpty && tree.elems.forall(isArrow) + then untpd.ref(defn.MapModule) + else untpd.ref(defn.SeqModule) + + // We construct and typecheck a term `maker(tree.elems)`, where `maker` + // is either a given instance of type ExpressibleAsCollectionLiteralClass + // or a default instance. The default instance is either Seq or Map, + // depending on the forms of `tree.elems`. We search for a type class if + // the expected type is a value type that is not an uninstantiated type variable. + val maker = pt match + case pt: TypeVar if !pt.isInstantiated => + defaultMaker + case pt: ValueType => + val tc = defn.ExpressibleAsCollectionLiteralClass.typeRef.appliedTo(pt) + val nestedCtx = ctx.fresh.setNewTyperState() + val maker = inContext(nestedCtx): + // Find given instance `witness` of type `ExpressibleAsCollectionLiteral[]` + val witness = inferImplicitArg(tc, tree.span.startPos) + if witness.tpe.isInstanceOf[SearchFailureType] then + val msg = missingArgMsg(witness, pt, "") + if isAmbiguousGiven(witness) then report.error(msg, tree.srcPos) + else typr.println(i"failed collection literal witness: ${msg.toString}") + defaultMaker + else + // Instantiate local type variables in witness.tpe, so that nested + // SeqLiterals don't get typed as default Seq due to first case above + def isLocal(tv: TypeVar) = + val state = tv.owningState + state != null && (state.get eq ctx.typerState) + instantiateSelected(witness.tpe, isLocal, minimize = false) + // Continue with typing `witness.fromLiteral` as the constructor + untpd.TypedSplice(witness.select(nme.fromLiteral)) + nestedCtx.typerState.commit() + maker + case _ => + defaultMaker + typed( + untpd.Apply(maker, tree.elems).withSpan(tree.span) + .showing(i"typed collection literal $tree ---> $result", typr) + , pt) + + if !tree.elemtpt.isEmpty then varargSeqLiteral + else if tree.isInstanceOf[Trees.JavaSeqLiteral[?]] then javaSeqLiteral + else collectionLiteral + end typedSeqLiteral def typedInlined(tree: untpd.Inlined, pt: Type)(using Context): Tree = throw new UnsupportedOperationException("cannot type check a Inlined node") diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 665b4f5144ba..58acc39a486a 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -278,6 +278,7 @@ SimpleExpr ::= SimpleRef | ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody] New(constr | templ) | ‘new’ TemplateBody | ‘(’ ExprsInParens ‘)’ Parens(exprs) + | ‘[’ ExprInBrackets ‘)’ SeqLiteral(exprs, TypeTree()) | SimpleExpr ‘.’ id Select(expr, id) | SimpleExpr ‘.’ MatchClause | SimpleExpr TypeArgs TypeApply(expr, args) @@ -299,6 +300,7 @@ TypeSplice ::= spliceId | ‘$’ ‘{’ Pattern ‘}’ -- when inside quoted type pattern -- deprecated syntax ExprsInParens ::= ExprInParens {‘,’ ExprInParens} | NamedExprInParens {‘,’ NamedExprInParens} +ExprsInBrackets ::= ExprInParens {‘,’ ExprInParens} ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here | Expr NamedExprInParens ::= id '=' ExprInParens diff --git a/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala b/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala new file mode 100644 index 000000000000..1e8eba0e2ca5 --- /dev/null +++ b/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala @@ -0,0 +1,56 @@ +package scala.compiletime +import reflect.ClassTag + +// This is in compiletime since it is an inline type class. Another +// possibility would be to put it in `scala.collection` + +/** A typeclass that supports creating collection-like data from + * collection literals `[x1,...,xN]`. + */ +trait ExpressibleAsCollectionLiteral[+Coll]: + + /** The element type of the created collection */ + type Elem + + /** The inline method that creates the collection */ + inline def fromLiteral(inline xs: Elem*): Coll + +object ExpressibleAsCollectionLiteral: + + // Some instances for standard collections. It would be good to have a method + // that works for all collections in stdlib. But to do that I believe we either + // have to put a given instance in Factory in stdlib, or write some macro + // method here. I have not found a straightforward way to build a collection + // of type `C` is all we know is the type. + + given seqFromLiteral: [T] => ExpressibleAsCollectionLiteral[collection.Seq[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*): Seq[T] = Seq(xs*) + + given mutableSeqFromLiteral: [T] => ExpressibleAsCollectionLiteral[collection.mutable.Seq[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*) = collection.mutable.Seq(xs*) + + given immutableSeqFromLiteral: [T] => ExpressibleAsCollectionLiteral[collection.immutable.Seq[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*) = collection.immutable.Seq(xs*) + + given vectorFromLiteral: [T] => ExpressibleAsCollectionLiteral[Vector[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*) = Vector[Elem](xs*) + + given listFromLiteral: [T] => ExpressibleAsCollectionLiteral[List[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*) = List(xs*) + + given arrayFromLiteral: [T: ClassTag] => ExpressibleAsCollectionLiteral[Array[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*) = Array(xs*) + + given iarrayFromLiteral: [T: ClassTag] => ExpressibleAsCollectionLiteral[IArray[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*) = IArray(xs*) + + given bitsetFromLiteral: ExpressibleAsCollectionLiteral[collection.immutable.BitSet]: + type Elem = Int + inline def fromLiteral(inline xs: Int*) = collection.immutable.BitSet(xs*) diff --git a/tests/neg/seqlits.check b/tests/neg/seqlits.check new file mode 100644 index 000000000000..1f5e45032f95 --- /dev/null +++ b/tests/neg/seqlits.check @@ -0,0 +1,11 @@ +-- [E172] Type Error: tests/neg/seqlits.scala:20:14 -------------------------------------------------------------------- +20 | val x: A = [1, 2, 3] // error: ambiguous + | ^^^^^^^ + |Ambiguous given instances: both given instance given_ExpressibleAsCollectionLiteral_B in object SeqLits and given instance given_ExpressibleAsCollectionLiteral_C in object SeqLits match type scala.compiletime.ExpressibleAsCollectionLiteral[A] +-- [E007] Type Mismatch Error: tests/neg/seqlits.scala:21:14 ----------------------------------------------------------- +21 | val y: D = [1, 2, 3] // error: type mismatch + | ^^^^^^^ + | Found: Seq[Int] + | Required: D + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/seqlits.scala b/tests/neg/seqlits.scala new file mode 100644 index 000000000000..36dbf44fa5ae --- /dev/null +++ b/tests/neg/seqlits.scala @@ -0,0 +1,21 @@ +import compiletime.ExpressibleAsCollectionLiteral + +class A + +case class B(xs: Int*) extends A +case class C(xs: Int*) extends A + +class D + +object SeqLits: + + given [T] => ExpressibleAsCollectionLiteral[B]: + type Elem = Int + inline def fromLiteral(inline xs: Int*): B = B(xs*) + + given [T] => ExpressibleAsCollectionLiteral[C]: + type Elem = Int + inline def fromLiteral(inline xs: Int*): C = C(xs*) + + val x: A = [1, 2, 3] // error: ambiguous + val y: D = [1, 2, 3] // error: type mismatch diff --git a/tests/run/seqlits.check b/tests/run/seqlits.check index cbf9734fe4df..32a600c0fc66 100644 --- a/tests/run/seqlits.check +++ b/tests/run/seqlits.check @@ -1,12 +1,18 @@ last was evaluated last was evaluated -Seq Vector(1, 2, 3, 4) -Vector Vector(1, 2, 3, 4) -last was evaluated -Task with elems List(1, 2, 3, 4) -last was evaluated last was evaluated -Seq Vector(1, 2, 3, 4) +Seq List(1, 2, 3, 4) Vector Vector(1, 2, 3, 4) +BitSet(1, 2, 4) last was evaluated Task with elems List(1, 2, 3, 4) +Vector() +List(hello, world) +List(hello, world) +List() +List(1, 2, 3) +List() +Map(1 -> one, 2 -> two) +List(List(1), List(2, 3), List()) +List(List(1), List(2, 3), List()) +Vector(Vector(1), Vector(2, 3), Vector()) diff --git a/tests/run/seqlits.scala b/tests/run/seqlits.scala index b341f529a378..7761d1b992e8 100644 --- a/tests/run/seqlits.scala +++ b/tests/run/seqlits.scala @@ -1,50 +1,50 @@ +import language.`3.7` import reflect.ClassTag +import compiletime.ExpressibleAsCollectionLiteral +import collection.immutable.BitSet /** Some delayed computation like a Mill Task */ case class Task[T](body: () => T) object SeqLits: - /** A typeclass to map sequence literals with `T` elements - * to some collection type `C`. - */ - trait FromArray[T, +C]: - inline def fromArray(inline xs: IArray[T]): C - - /** An emulation of a sequence literal like [1, 2, 3]. Since we don't have that - * syntax yet, we express it here as `seqLit(1, 2, 3)`. - */ - inline def seqLit[T: ClassTag, C](inline xs: T*)(using inline f: FromArray[T, C]): C = - f.fromArray(IArray(xs*)) - - inline def seqLit2[T, C](inline xs: IArray[T])(using inline f: FromArray[T, C]): C = - f.fromArray(xs) - - /** Straightfoward mapping to Seq */ - given [T] => FromArray[T, Seq[T]]: - inline def fromArray(inline xs: IArray[T]) = Seq(xs*) - - /** A more specific mapping to Vector */ - given [T] => FromArray[T, Vector[T]]: - inline def fromArray(inline xs: IArray[T]) = Vector(xs*) - - /** A delaying mapping to Task */ - given [T] => FromArray[T, Task[Seq[T]]]: - inline def fromArray(inline xs: IArray[T]) = Task(() => Seq(xs*)) + given [T] => ExpressibleAsCollectionLiteral[Task[Seq[T]]]: + type Elem = T + inline def fromLiteral(inline xs: T*): Task[Seq[T]] = Task(() => Seq(xs*)) def last: Int = { println("last was evaluated"); 4 } - @main def Test = - val s: Seq[Int] = seqLit(1, 2, 3, last) - val v: Vector[Int] = seqLit(1, 2, 3, last) - val t: Task[Seq[Int]] = seqLit(1, 2, 3, last) + @main def Test = + val s: Seq[Int] = [1, 2, 3, last] + val v: Vector[Int] = [1, 2, 3, last] + val t: Task[Seq[Int]] = [1, 2, 3, last] + val ve: Vector[String] = [] + val a: Array[String] = ["hello", "world"] + val ia: IArray[String] = ["hello", "world"] + val iae: IArray[String] = [] + val u = [1, 2, 3] + val _: Seq[Int] = u + val e = [] + val _: Seq[Int] = e + val m = [1 -> "one", 2 -> "two"] + val _: Map[Int, String] = m + val bs: BitSet = [1, 2, 4, last] + val ss: Seq[Seq[Int]] = [[1], [2, 3], []] + val ss2 = [[1], [2, 3], []] + val _: Seq[Seq[Int]] = ss2 + val vs: Vector[Vector[Int]] = [[1], [2, 3], []] + println(s"Seq $s") println(s"Vector $v") + println(bs) println(s"${t.getClass.getSimpleName} with elems ${t.body()}") - - val s2: Seq[Int] = seqLit2(IArray(1, 2, 3, last)) - val v2: Vector[Int] = seqLit2(IArray(1, 2, 3, last)) - val t2: Task[Seq[Int]] = seqLit2(IArray(1, 2, 3, last)) - println(s"Seq $s2") - println(s"Vector $v2") - println(s"${t2.getClass.getSimpleName} with elems ${t2.body()}") + println(ve) + println(a.toList) + println(ia.toList) + println(iae.toList) + println(u) + println(e) + println(m) + println(ss) + println(ss2) + println(vs) From f16fede9c33ee7796a2750bbb38607b2175db545 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 25 Nov 2024 15:36:05 +0100 Subject: [PATCH 05/15] Require a language import for enabling collection literals --- compiler/src/dotty/tools/dotc/config/Feature.scala | 1 + compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- .../scala/compiletime/ExpressibleAsCollectionLiteral.scala | 6 ++++-- library/src/scala/runtime/stdLibPatches/language.scala | 7 +++++++ .../stdlibExperimentalDefinitions.scala | 7 ++++++- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 8b9a64924ace..eecc93e029d7 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -39,6 +39,7 @@ object Feature: val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors") val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions") val betterFors = experimental("betterFors") + val collectionLiterals = experimental("collectionLiterals") def experimentalAutoEnableFeatures(using Context): List[TermName] = defn.languageExperimentalFeatures diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 0f4f93073e73..cb3d62a598ef 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2746,7 +2746,7 @@ object Parsers { case LBRACE | INDENT => canApply = false blockExpr() - case LBRACKET => + case LBRACKET if in.featureEnabled(Feature.collectionLiterals) => inBrackets: SeqLiteral(exprsInBrackets(), TypeTree()) case QUOTE => diff --git a/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala b/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala index 1e8eba0e2ca5..2443a3dd3f14 100644 --- a/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala +++ b/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala @@ -1,4 +1,6 @@ package scala.compiletime + +import annotation.experimental import reflect.ClassTag // This is in compiletime since it is an inline type class. Another @@ -7,7 +9,7 @@ import reflect.ClassTag /** A typeclass that supports creating collection-like data from * collection literals `[x1,...,xN]`. */ -trait ExpressibleAsCollectionLiteral[+Coll]: +@experimental trait ExpressibleAsCollectionLiteral[+Coll]: /** The element type of the created collection */ type Elem @@ -15,7 +17,7 @@ trait ExpressibleAsCollectionLiteral[+Coll]: /** The inline method that creates the collection */ inline def fromLiteral(inline xs: Elem*): Coll -object ExpressibleAsCollectionLiteral: +@experimental object ExpressibleAsCollectionLiteral: // Some instances for standard collections. It would be good to have a method // that works for all collections in stdlib. But to do that I believe we either diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 547710d55293..35712da9b3ca 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -140,6 +140,13 @@ object language: */ @compileTimeOnly("`betterFors` can only be used at compile time in import statements") object betterFors + + /** Experimental support for collection literals + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/collection-literals]] + */ + @compileTimeOnly("`collectionLiterals` can only be used at compile time in import statements") + object collectionLiterals end experimental /** The deprecated object contains features that are no longer officially suypported in Scala. diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 65e3a730ee7e..f6dfeec827b9 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -93,7 +93,12 @@ val experimentalDefinitionInLibrary = Set( "scala.quoted.runtime.Patterns$.higherOrderHoleWithTypes", // New feature: SIP 57 - runtimeChecked replacement of @unchecked - "scala.Predef$.runtimeChecked", "scala.annotation.internal.RuntimeChecked" + "scala.Predef$.runtimeChecked", + "scala.annotation.internal.RuntimeChecked", + + // New feature: Collection literals + "scala.compiletime.ExpressibleAsCollectionLiteral", + "scala.compiletime.ExpressibleAsCollectionLiteral$", ) From d357b9ba3fb68741e4ac7d06f92dfc17cb28b4f2 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 25 Nov 2024 15:40:13 +0100 Subject: [PATCH 06/15] Use a different scheme for deciding when to use the default We now don't try to instantiate selected type variables. Instead, we use a default as fallback if the expected type is underspecified according to the definition in Implicits. This is simpler and more expressive. --- .../dotty/tools/dotc/typer/Implicits.scala | 26 +++++++-------- .../src/dotty/tools/dotc/typer/Typer.scala | 33 ++++++++----------- .../ExpressibleAsCollectionLiteral.scala | 20 +++++++++++ tests/neg/seqlits.check | 29 +++++++++++++--- tests/neg/seqlits.scala | 5 +++ tests/run/seqlits.check | 5 +++ tests/run/seqlits.scala | 13 ++++++++ 7 files changed, 94 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 38a80abcc571..ce4970f2c650 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -618,6 +618,19 @@ object Implicits: def msg(using Context): Message = em"${errors.map(_.msg).mkString("\n")}" } + + private def isUnderSpecifiedArgument(tp: Type)(using Context): Boolean = + tp.isRef(defn.NothingClass) || tp.isRef(defn.NullClass) || (tp eq NoPrefix) + + def isUnderspecified(tp: Type)(using Context): Boolean = tp.stripTypeVar match + case tp: WildcardType => + !tp.optBounds.exists || isUnderspecified(tp.optBounds.hiBound) + case tp: ViewProto => + isUnderspecified(tp.resType) + || tp.resType.isRef(defn.UnitClass) + || isUnderSpecifiedArgument(tp.argType.widen) + case _ => + tp.isAny || tp.isAnyRef end Implicits import Implicits.* @@ -1651,19 +1664,6 @@ trait Implicits: res end searchImplicit - def isUnderSpecifiedArgument(tp: Type): Boolean = - tp.isRef(defn.NothingClass) || tp.isRef(defn.NullClass) || (tp eq NoPrefix) - - private def isUnderspecified(tp: Type): Boolean = tp.stripTypeVar match - case tp: WildcardType => - !tp.optBounds.exists || isUnderspecified(tp.optBounds.hiBound) - case tp: ViewProto => - isUnderspecified(tp.resType) - || tp.resType.isRef(defn.UnitClass) - || isUnderSpecifiedArgument(tp.argType.widen) - case _ => - tp.isAny || tp.isAnyRef - /** Search implicit in context `ctxImplicits` or else in implicit scope * of expected type if `ctxImplicits == null`. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ce0f47c61d99..8a5c6ce42ac4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2439,32 +2439,25 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // is either a given instance of type ExpressibleAsCollectionLiteralClass // or a default instance. The default instance is either Seq or Map, // depending on the forms of `tree.elems`. We search for a type class if - // the expected type is a value type that is not an uninstantiated type variable. + // the expected type is a value type that is not underspeficied for implicit search. val maker = pt match - case pt: TypeVar if !pt.isInstantiated => - defaultMaker - case pt: ValueType => + case pt: ValueType if !Implicits.isUnderspecified(wildApprox(pt)) => val tc = defn.ExpressibleAsCollectionLiteralClass.typeRef.appliedTo(pt) val nestedCtx = ctx.fresh.setNewTyperState() - val maker = inContext(nestedCtx): - // Find given instance `witness` of type `ExpressibleAsCollectionLiteral[]` - val witness = inferImplicitArg(tc, tree.span.startPos) - if witness.tpe.isInstanceOf[SearchFailureType] then - val msg = missingArgMsg(witness, pt, "") - if isAmbiguousGiven(witness) then report.error(msg, tree.srcPos) - else typr.println(i"failed collection literal witness: ${msg.toString}") + // Find given instance `witness` of type `ExpressibleAsCollectionLiteral[]` + val witness = inferImplicitArg(tc, tree.span.startPos) + def errMsg = missingArgMsg(witness, pt, "") + typr.println(i"infer for $tree with $tc = $witness, ${ctx.typerState.constraint}") + witness.tpe match + case _: AmbiguousImplicits => + report.error(errMsg, tree.srcPos) defaultMaker - else - // Instantiate local type variables in witness.tpe, so that nested - // SeqLiterals don't get typed as default Seq due to first case above - def isLocal(tv: TypeVar) = - val state = tv.owningState - state != null && (state.get eq ctx.typerState) - instantiateSelected(witness.tpe, isLocal, minimize = false) + case _: SearchFailureType => + typr.println(i"failed collection literal witness: ${errMsg.toString}") + defaultMaker + case _ => // Continue with typing `witness.fromLiteral` as the constructor untpd.TypedSplice(witness.select(nme.fromLiteral)) - nestedCtx.typerState.commit() - maker case _ => defaultMaker typed( diff --git a/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala b/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala index 2443a3dd3f14..a4a868202fe2 100644 --- a/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala +++ b/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala @@ -53,6 +53,26 @@ import reflect.ClassTag type Elem = T inline def fromLiteral(inline xs: T*) = IArray(xs*) + given arrayBufferFromLiteral: [T: ClassTag] => ExpressibleAsCollectionLiteral[collection.mutable.ArrayBuffer[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*) = collection.mutable.ArrayBuffer(xs*) + + given setFromLiteral: [T] => ExpressibleAsCollectionLiteral[Set[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*) = Set(xs*) + + given hashSetFromLiteral: [T] => ExpressibleAsCollectionLiteral[collection.mutable.HashSet[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*) = collection.mutable.HashSet(xs*) + given bitsetFromLiteral: ExpressibleAsCollectionLiteral[collection.immutable.BitSet]: type Elem = Int inline def fromLiteral(inline xs: Int*) = collection.immutable.BitSet(xs*) + + given mapFromLiteral: [K, V] => ExpressibleAsCollectionLiteral[Map[K, V]]: + type Elem = (K, V) + inline def fromLiteral(inline xs: (K, V)*) = Map(xs*) + + given hashMapFromLiteral: [K, V] => ExpressibleAsCollectionLiteral[collection.mutable.HashMap[K, V]]: + type Elem = (K, V) + inline def fromLiteral(inline xs: (K, V)*) = collection.mutable.HashMap(xs*) diff --git a/tests/neg/seqlits.check b/tests/neg/seqlits.check index 1f5e45032f95..d68dce39b9cd 100644 --- a/tests/neg/seqlits.check +++ b/tests/neg/seqlits.check @@ -1,11 +1,32 @@ --- [E172] Type Error: tests/neg/seqlits.scala:20:14 -------------------------------------------------------------------- -20 | val x: A = [1, 2, 3] // error: ambiguous +-- [E172] Type Error: tests/neg/seqlits.scala:23:14 -------------------------------------------------------------------- +23 | val x: A = [1, 2, 3] // error: ambiguous | ^^^^^^^ |Ambiguous given instances: both given instance given_ExpressibleAsCollectionLiteral_B in object SeqLits and given instance given_ExpressibleAsCollectionLiteral_C in object SeqLits match type scala.compiletime.ExpressibleAsCollectionLiteral[A] --- [E007] Type Mismatch Error: tests/neg/seqlits.scala:21:14 ----------------------------------------------------------- -21 | val y: D = [1, 2, 3] // error: type mismatch +-- [E007] Type Mismatch Error: tests/neg/seqlits.scala:24:14 ----------------------------------------------------------- +24 | val y: D = [1, 2, 3] // error: type mismatch | ^^^^^^^ | Found: Seq[Int] | Required: D | | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/seqlits.scala:26:39 ----------------------------------------------------------- +26 | val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: type mismatch // error // error + | ^^^^^^^^ + | Found: (Seq[Int], Seq[Int]) + | Required: (scala.collection.immutable.BitSet, Seq[Int]) + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/seqlits.scala:26:51 ----------------------------------------------------------- +26 | val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: type mismatch // error // error + | ^^^^^^^^^^^^^^ + | Found: (Seq[Int], Seq[Int]) + | Required: (scala.collection.immutable.BitSet, Seq[Int]) + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/seqlits.scala:26:69 ----------------------------------------------------------- +26 | val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: type mismatch // error // error + | ^^^^^ + | Found: (Seq[Int], Seq[Nothing]) + | Required: (scala.collection.immutable.BitSet, Seq[Int]) + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/seqlits.scala b/tests/neg/seqlits.scala index 36dbf44fa5ae..9739255aa218 100644 --- a/tests/neg/seqlits.scala +++ b/tests/neg/seqlits.scala @@ -1,4 +1,7 @@ +import language.`3.7` import compiletime.ExpressibleAsCollectionLiteral +import language.experimental.collectionLiterals +import collection.immutable.BitSet class A @@ -19,3 +22,5 @@ object SeqLits: val x: A = [1, 2, 3] // error: ambiguous val y: D = [1, 2, 3] // error: type mismatch + + val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: type mismatch // error // error \ No newline at end of file diff --git a/tests/run/seqlits.check b/tests/run/seqlits.check index 32a600c0fc66..fcdc6bcf4f99 100644 --- a/tests/run/seqlits.check +++ b/tests/run/seqlits.check @@ -16,3 +16,8 @@ Map(1 -> one, 2 -> two) List(List(1), List(2, 3), List()) List(List(1), List(2, 3), List()) Vector(Vector(1), Vector(2, 3), Vector()) +ArrayBuffer(Set(hello, world), Set()) +Set(BitSet(1), BitSet(2, 3), BitSet()) +Map(1 -> BitSet(1), 2 -> BitSet(1, 2), 0 -> BitSet()) +HashMap(0 -> List(), 1 -> List(BitSet(1), BitSet(2, 3)), 2 -> List(BitSet())) +Map(BitSet(1) -> List(1), BitSet(0, 2) -> List(1, 2), BitSet(0) -> List()) diff --git a/tests/run/seqlits.scala b/tests/run/seqlits.scala index 7761d1b992e8..f23370186af1 100644 --- a/tests/run/seqlits.scala +++ b/tests/run/seqlits.scala @@ -2,6 +2,8 @@ import language.`3.7` import reflect.ClassTag import compiletime.ExpressibleAsCollectionLiteral import collection.immutable.BitSet +import collection.mutable.{ArrayBuffer, HashMap} +import language.experimental.collectionLiterals /** Some delayed computation like a Mill Task */ case class Task[T](body: () => T) @@ -33,6 +35,12 @@ object SeqLits: val ss2 = [[1], [2, 3], []] val _: Seq[Seq[Int]] = ss2 val vs: Vector[Vector[Int]] = [[1], [2, 3], []] + val ab: ArrayBuffer[Set[String]] = [["hello", "world"], []] + val sbs: Set[BitSet] = [[1], [2, 3], []] + val mbs: Map[Int, BitSet] = [1 -> [1], 2 -> [1, 2], 0 -> []] + val hbs: HashMap[Int, Seq[BitSet]] = [1 -> [[1], [2, 3]], 2 -> [[]], 0 -> []] + // val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: keys get default value Seq + val mbss: Map[BitSet, Seq[Int]] = [([1], [1]), ([0, 2], [1, 2]), ([0], [])] // ok println(s"Seq $s") println(s"Vector $v") @@ -48,3 +56,8 @@ object SeqLits: println(ss) println(ss2) println(vs) + println(ab) + println(sbs) + println(mbs) + println(hbs) + println(mbss) From 79e07cf6438c29d06795b50ef16810cfa95af4d3 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 25 Nov 2024 16:32:20 +0100 Subject: [PATCH 07/15] More tests --- tests/neg/seqlits.check | 14 ++++++++++++++ tests/neg/seqlits.scala | 12 +++++++++++- tests/run/seqlits.scala | 11 +++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/neg/seqlits.check b/tests/neg/seqlits.check index d68dce39b9cd..67e9c0e6120f 100644 --- a/tests/neg/seqlits.check +++ b/tests/neg/seqlits.check @@ -30,3 +30,17 @@ | Required: (scala.collection.immutable.BitSet, Seq[Int]) | | longer explanation available when compiling with `-explain` +-- [E134] Type Error: tests/neg/seqlits.scala:30:11 -------------------------------------------------------------------- +30 | val xx = f([1, 2, 3]) // error: no matching alternatives for Seq[Int] + | ^ + | None of the overloaded alternatives of method f in object SeqLits with types + | [A](xs: Vector[A]): Vector[A] + | [A](xs: List[A]): List[A] + | match arguments (Seq[Int]) +-- [E134] Type Error: tests/neg/seqlits.scala:34:11 -------------------------------------------------------------------- +34 | val yy = g([1, 2, 3]) // error: no matching alternatives for Seq[Int] (even if one method is more specific than the other) + | ^ + | None of the overloaded alternatives of method g in object SeqLits with types + | [A](xs: scala.collection.immutable.HashSet[A]): Set[A] + | [A](xs: Set[A]): Set[A] + | match arguments (Seq[Int]) diff --git a/tests/neg/seqlits.scala b/tests/neg/seqlits.scala index 9739255aa218..c3166e925a61 100644 --- a/tests/neg/seqlits.scala +++ b/tests/neg/seqlits.scala @@ -23,4 +23,14 @@ object SeqLits: val x: A = [1, 2, 3] // error: ambiguous val y: D = [1, 2, 3] // error: type mismatch - val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: type mismatch // error // error \ No newline at end of file + val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: type mismatch // error // error + + def f[A](xs: List[A]) = xs + def f[A](xs: Vector[A]) = xs + val xx = f([1, 2, 3]) // error: no matching alternatives for Seq[Int] + + def g[A](xs: Set[A]): Set[A] = xs + def g[A](xs: collection.immutable.HashSet[A]): Set[A] = xs + val yy = g([1, 2, 3]) // error: no matching alternatives for Seq[Int] (even if one method is more specific than the other) + + diff --git a/tests/run/seqlits.scala b/tests/run/seqlits.scala index f23370186af1..074dc9530d93 100644 --- a/tests/run/seqlits.scala +++ b/tests/run/seqlits.scala @@ -16,6 +16,12 @@ object SeqLits: def last: Int = { println("last was evaluated"); 4 } + def f1[A](xs: A, ys: A) = ys + def f2[A](xs: Vector[A]) = xs + + def g[A](xs: Set[A]): Set[A] = xs + def g[A](xs: collection.immutable.HashSet[A]): Set[A] = xs + @main def Test = val s: Seq[Int] = [1, 2, 3, last] val v: Vector[Int] = [1, 2, 3, last] @@ -42,6 +48,11 @@ object SeqLits: // val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: keys get default value Seq val mbss: Map[BitSet, Seq[Int]] = [([1], [1]), ([0, 2], [1, 2]), ([0], [])] // ok + val x1 = f1(Vector(1, 2, 3), [3, 4, 5]) + val _: Seq[Int] = x1 + val x2 = f2([1, 2, 3]) + val _: Vector[Int] = x2 + println(s"Seq $s") println(s"Vector $v") println(bs) From 888293a7df1143a4173716ceeb1af683faa8c1ac Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 25 Nov 2024 18:28:02 +0100 Subject: [PATCH 08/15] Add a doc page --- .../experimental/collection-literals.md | 90 +++++++++++++++++++ docs/sidebar.yml | 1 + tests/run/seqlits.scala | 7 ++ 3 files changed, 98 insertions(+) create mode 100644 docs/_docs/reference/experimental/collection-literals.md diff --git a/docs/_docs/reference/experimental/collection-literals.md b/docs/_docs/reference/experimental/collection-literals.md new file mode 100644 index 000000000000..e65b8204d870 --- /dev/null +++ b/docs/_docs/reference/experimental/collection-literals.md @@ -0,0 +1,90 @@ +--- +layout: doc-page +title: "Collection Literals" +redirectFrom: /docs/reference/other-new-features/collection-literals.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/collection-literals.html +--- + + +Support for collection literals is enabled by the experimental language import +```scala +import scala.language.experimental.collectionLiterals +``` +Collection literals are comma-separated sequences of expressions, like these: +```scala + val oneTwoThree = [1, 2, 3] + val anotherLit = [math.Pi, math.cos(2.0), math.E * 3.0] + val diag = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + val empty = [] + val mapy = [1 -> "one", 2 -> "two", 3 -> "three"] +``` +The type of a collection literal depends on the expected type. If there is no expected type (as in the examples above) a collection literal is of type `Seq`, except if it consists exclusively elements of the form `a -> b`, then it is of type `Map`. For instance, the literals above would +get inferred types as follows. +```scala + val oneTwoThree: Seq[Int] = [1, 2, 3] + val anotherLit: Seq[Double] = [math.Pi, math.cos(2.0), math.E * 3.0] + val diag: Seq[Seq[Int]] = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + val empty: Seq[Nothing] = [] + val mapy: Map[Int, String] = [1 -> "one", 2 -> "two", 3 -> "three"] +``` +If there is an expected type `E`, the compiler will search for a given instance of the +type class `ExpressibleAsCollectionLiteral[E]`. This type class is defined in package `scala.compiletime` as follows: +```scala + trait ExpressibleAsCollectionLiteral[+Coll]: + + /** The element type of the created collection */ + type Elem + + /** The inline method that creates the collection */ + inline def fromLiteral(inline xs: Elem*): Coll +``` +If a best matching instance `ecl` is found, its `fromLiteral` method is used to convert +the elements of the literal to the expected type. If the search is ambiguous, it will be +reported as an error. If no matching instance is found, the literal will be typed by the default scheme as if there was no expected type. + +The companion object of `ExpressibleAsCollectionLiteral` contains a number of given instances for standard collection types. For instance, there is: +```scala + given vectorFromLiteral: [T] => ExpressibleAsCollectionLiteral[Vector[T]]: + type Elem = T + inline def fromLiteral(inline xs: T*) = Vector[Elem](xs*) +``` +Hence, the definition +```scala + val v: Vector[Int] = [1, 2, 3] +``` +would be expanded by the compiler to +```scala + val v: Vector[Int] = vectorFromLiteral.fromLiteral(1, 2, 3) +``` +After inlining, this produces +```scala + val v: Vector[Int] = Vector[Int](1, 2, 3) +``` +Using this scheme, the literals we have seen earlier could also be given alternative types like these: +```scala + val oneTwoThree: Vector[Int] = [1, 2, 3] + val anotherLit: IArray[Double] = [math.Pi, math.cos(2.0), math.E * 3.0] + val diag: Array[Array[Int]] = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + val empty: ArrayBuffer[Object] = [] + val mapy: HashMap[Int, String] = [1 -> "one", 2 -> "two", 3 -> "three"] +``` + +**Notes** + + - Since the fromLiteral method in `ExpressibleAsCollectionLiteral` is an inline method with inline arguments, given instances can implement it as a macro. + + - The precise meaning of "is there an expected type?" is as follows: There is no expected + type if the expected type known from the context is _underdefined_ as specified for + implicit search. That is, implicit search for a conversion to the expected type would fail with an error message that contains a note like this: + ``` + Note that implicit conversions were not tried because the result of an implicit conversion|must be more specific than ... + ``` + Concretely, this is the case for Wildcard types `?`, `Any`, `AnyRef`, or type variables + bounded by one of these types. + +**Syntax** + +``` +SimpleExpr ::= ... + | ‘[’ ExprInParens {‘,’ ExprInParens} ‘]’ +``` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index a306d8bdf274..831bda666158 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -163,6 +163,7 @@ subsection: - page: reference/experimental/typeclasses.md - page: reference/experimental/runtimeChecked.md - page: reference/experimental/better-fors.md + - page: reference/experimental/collection-literals.md - page: reference/syntax.md - title: Language Versions index: reference/language-versions/language-versions.md diff --git a/tests/run/seqlits.scala b/tests/run/seqlits.scala index 074dc9530d93..1b9b7fe471fd 100644 --- a/tests/run/seqlits.scala +++ b/tests/run/seqlits.scala @@ -72,3 +72,10 @@ object SeqLits: println(mbs) println(hbs) println(mbss) + + + val oneTwoThree: Vector[Int] = [1, 2, 3] + val anotherLit: IArray[Double] = [math.Pi, math.cos(2.0), math.E * 3.0] + val diag: Array[Array[Int]] = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + val empty: ArrayBuffer[Object] = [] + val mapy: HashMap[Int, String] = [1 -> "one", 2 -> "two", 3 -> "three"] From 176ba1bcc5fc92ca5e690e10f87e3f2dbf49bc9f Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 25 Nov 2024 18:43:11 +0100 Subject: [PATCH 09/15] Add Mimafilters --- project/MiMaFilters.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 00e7153bcb83..6d783129b233 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -13,6 +13,8 @@ object MiMaFilters { ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.quotedPatternsWithPolymorphicFunctions"), ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$quotedPatternsWithPolymorphicFunctions$"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.Patterns.higherOrderHoleWithTypes"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.collectionLiterals"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$collectionLiterals$"), ), // Additions since last LTS From e7c10b10f1fb4b4bc90c6de5d7fcb5b2cd22fd4b Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 25 Nov 2024 19:59:56 +0100 Subject: [PATCH 10/15] Add tests of literals with elements of mmixed types --- tests/run/seqlits.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/run/seqlits.scala b/tests/run/seqlits.scala index 1b9b7fe471fd..1c5c320d00af 100644 --- a/tests/run/seqlits.scala +++ b/tests/run/seqlits.scala @@ -47,6 +47,13 @@ object SeqLits: val hbs: HashMap[Int, Seq[BitSet]] = [1 -> [[1], [2, 3]], 2 -> [[]], 0 -> []] // val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: keys get default value Seq val mbss: Map[BitSet, Seq[Int]] = [([1], [1]), ([0, 2], [1, 2]), ([0], [])] // ok + val mixed1 = [1, 1.0, -2] + val _: Seq[Double] = mixed1 + val mixed2 = [1, true] + val _: Seq[Int | Boolean] = mixed2 + val anInt = 3 + val mixed3 = [anInt, 3.0] + val _: Seq[Int | Double] = mixed3 val x1 = f1(Vector(1, 2, 3), [3, 4, 5]) val _: Seq[Int] = x1 From 43bc40e2a6903ea4eb10fbe0500c0a774f577b40 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 26 Nov 2024 10:10:03 +0100 Subject: [PATCH 11/15] Fix syntax.md typo Also, test infix operations taking [...] as a right operand. --- docs/_docs/internals/syntax.md | 3 +-- tests/run/seqlists.check | 23 +++++++++++++++++++++++ tests/run/seqlits.check | 2 +- tests/run/seqlits.scala | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 tests/run/seqlists.check diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 58acc39a486a..381eb08a3e8e 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -278,7 +278,7 @@ SimpleExpr ::= SimpleRef | ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody] New(constr | templ) | ‘new’ TemplateBody | ‘(’ ExprsInParens ‘)’ Parens(exprs) - | ‘[’ ExprInBrackets ‘)’ SeqLiteral(exprs, TypeTree()) + | ‘[’ ExprInParens {‘,’ ExprInParens} ‘]’ SeqLiteral(exprs, TypeTree()) | SimpleExpr ‘.’ id Select(expr, id) | SimpleExpr ‘.’ MatchClause | SimpleExpr TypeArgs TypeApply(expr, args) @@ -300,7 +300,6 @@ TypeSplice ::= spliceId | ‘$’ ‘{’ Pattern ‘}’ -- when inside quoted type pattern -- deprecated syntax ExprsInParens ::= ExprInParens {‘,’ ExprInParens} | NamedExprInParens {‘,’ NamedExprInParens} -ExprsInBrackets ::= ExprInParens {‘,’ ExprInParens} ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here | Expr NamedExprInParens ::= id '=' ExprInParens diff --git a/tests/run/seqlists.check b/tests/run/seqlists.check new file mode 100644 index 000000000000..8601ed7aa559 --- /dev/null +++ b/tests/run/seqlists.check @@ -0,0 +1,23 @@ +last was evaluated +last was evaluated +last was evaluated +Seq List(1, 2, 3, 4) +Vector Vector(1, 2, 3, 4) +BitSet(1, 2, 4) +last was evaluated +Task with elems List(1, 2, 3, 4) +Vector() +List(hello, world) +List(hello, world) +List() +List(1, 2, 3) +List() +Map(1 -> one, 2 -> two) +List(List(1), List(2, 3), List()) +List(List(1), List(2, 3), List()) +Vector(Vector(1), Vector(2, 3), Vector()) +ArrayBuffer(Set(hello, world), Set(), Set(!)) +Set(BitSet(1), BitSet(2, 3), BitSet()) +Map(1 -> BitSet(1), 2 -> BitSet(1, 2), 0 -> BitSet()) +HashMap(0 -> List(), 1 -> List(BitSet(1), BitSet(2, 3)), 2 -> List(BitSet())) +Map(BitSet(1) -> List(1), BitSet(0, 2) -> List(1, 2), BitSet(0) -> List()) diff --git a/tests/run/seqlits.check b/tests/run/seqlits.check index fcdc6bcf4f99..8601ed7aa559 100644 --- a/tests/run/seqlits.check +++ b/tests/run/seqlits.check @@ -16,7 +16,7 @@ Map(1 -> one, 2 -> two) List(List(1), List(2, 3), List()) List(List(1), List(2, 3), List()) Vector(Vector(1), Vector(2, 3), Vector()) -ArrayBuffer(Set(hello, world), Set()) +ArrayBuffer(Set(hello, world), Set(), Set(!)) Set(BitSet(1), BitSet(2, 3), BitSet()) Map(1 -> BitSet(1), 2 -> BitSet(1, 2), 0 -> BitSet()) HashMap(0 -> List(), 1 -> List(BitSet(1), BitSet(2, 3)), 2 -> List(BitSet())) diff --git a/tests/run/seqlits.scala b/tests/run/seqlits.scala index 1c5c320d00af..90ef36df07b8 100644 --- a/tests/run/seqlits.scala +++ b/tests/run/seqlits.scala @@ -42,6 +42,7 @@ object SeqLits: val _: Seq[Seq[Int]] = ss2 val vs: Vector[Vector[Int]] = [[1], [2, 3], []] val ab: ArrayBuffer[Set[String]] = [["hello", "world"], []] + ab += ["!"] val sbs: Set[BitSet] = [[1], [2, 3], []] val mbs: Map[Int, BitSet] = [1 -> [1], 2 -> [1, 2], 0 -> []] val hbs: HashMap[Int, Seq[BitSet]] = [1 -> [[1], [2, 3]], 2 -> [[]], 0 -> []] From dbfd03eb0be7d909032fcf68e1db4747153c50ef Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 26 Nov 2024 17:39:10 +0100 Subject: [PATCH 12/15] Polishing Some corrections in docs, fix typos, drop unused code. --- .../src/dotty/tools/dotc/core/Definitions.scala | 1 - compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 13 ++++++++++--- .../src/dotty/tools/dotc/typer/Inferencing.scala | 7 ++----- .../reference/experimental/collection-literals.md | 10 +++------- .../ExpressibleAsCollectionLiteral.scala | 9 +++++---- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 80c5d1d033f1..41df762df26b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -583,7 +583,6 @@ class Definitions { def IArrayModuleClass: Symbol = IArrayModule.moduleClass @tu lazy val ExpressibleAsCollectionLiteralClass: ClassSymbol = requiredClass("scala.compiletime.ExpressibleAsCollectionLiteral") - @tu lazy val ExpressibleACL_fromLiteral: Symbol = ExpressibleAsCollectionLiteralClass.requiredMethod("fromLiteral") @tu lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", java.lang.Void.TYPE, UnitEnc, nme.specializedTypeNames.Void) def UnitClass(using Context): ClassSymbol = UnitType.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ea4dc80730d6..c5937074f4bc 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4913,7 +4913,7 @@ object Types extends TypeUtils { /** The state owning the variable. This is at first `creatorState`, but it can * be changed to an enclosing state on a commit. */ - private[dotc] var owningState: WeakReference[TyperState] | Null = + private[core] var owningState: WeakReference[TyperState] | Null = if (creatorState == null) null else new WeakReference(creatorState) /** The nesting level of this type variable in the current typer state. This is usually diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ce4970f2c650..f668ab8148ea 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -443,12 +443,10 @@ object Implicits: extends SearchResult with RefAndLevel with Showable: final def found = ref :: Nil - def isAmbiguousGiven(tree: Tree) = tree.tpe.isInstanceOf[AmbiguousImplicits | TooUnspecific] - /** A failed search */ case class SearchFailure(tree: Tree) extends SearchResult { require(tree.tpe.isInstanceOf[SearchFailureType], s"unexpected type for ${tree}") - final def isAmbiguous: Boolean = isAmbiguousGiven(tree) + final def isAmbiguous: Boolean = tree.tpe.isInstanceOf[AmbiguousImplicits | TooUnspecific] final def reason: SearchFailureType = tree.tpe.asInstanceOf[SearchFailureType] final def found = tree.tpe match case tpe: AmbiguousImplicits => tpe.alt1.ref :: tpe.alt2.ref :: Nil @@ -622,6 +620,15 @@ object Implicits: private def isUnderSpecifiedArgument(tp: Type)(using Context): Boolean = tp.isRef(defn.NothingClass) || tp.isRef(defn.NullClass) || (tp eq NoPrefix) + /** Is `tp` not specific enough to warrant an implicit search for it? + * This is the case for + * - `?`, `Any`, `AnyRef`, + * - conversions from a bottom type, or to an underspecified type, or to `Unit + * - bounded wildcard types with underspecified upper bound + * The method is usually called after transforming a type with `wildApprox`, + * which means that type variables with underspecified upper constraints are also + * underspecified. + */ def isUnderspecified(tp: Type)(using Context): Boolean = tp.stripTypeVar match case tp: WildcardType => !tp.optBounds.exists || isUnderspecified(tp.optBounds.hiBound) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index c0be3b58bd49..3d963011a522 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -58,13 +58,10 @@ object Inferencing { * The method is called to instantiate type variables before an implicit search. */ def instantiateSelected(tp: Type, tvars: List[Type])(using Context): Unit = - if tvars.nonEmpty then instantiateSelected(tp, tvars.contains, minimize = true) - - def instantiateSelected(tp: Type, cond: TypeVar => Boolean, minimize: Boolean)(using Context): Unit = IsFullyDefinedAccumulator( new ForceDegree.Value(IfBottom.flip): - override def appliesTo(tvar: TypeVar) = cond(tvar), - minimizeSelected = minimize + override def appliesTo(tvar: TypeVar) = tvars.contains(tvar), + minimizeSelected = true ).process(tp) /** Instantiate any type variables in `tp` whose bounds contain a reference to diff --git a/docs/_docs/reference/experimental/collection-literals.md b/docs/_docs/reference/experimental/collection-literals.md index e65b8204d870..95dd225d0fed 100644 --- a/docs/_docs/reference/experimental/collection-literals.md +++ b/docs/_docs/reference/experimental/collection-literals.md @@ -74,13 +74,9 @@ Using this scheme, the literals we have seen earlier could also be given alterna - Since the fromLiteral method in `ExpressibleAsCollectionLiteral` is an inline method with inline arguments, given instances can implement it as a macro. - The precise meaning of "is there an expected type?" is as follows: There is no expected - type if the expected type known from the context is _underdefined_ as specified for - implicit search. That is, implicit search for a conversion to the expected type would fail with an error message that contains a note like this: - ``` - Note that implicit conversions were not tried because the result of an implicit conversion|must be more specific than ... - ``` - Concretely, this is the case for Wildcard types `?`, `Any`, `AnyRef`, or type variables - bounded by one of these types. + type if the expected type known from the context is _under-specified_, as it is defined for + implicit search. That is, an implicit search for a given of the type would not be + attempted because the type is not specific enough. Concretely, this is the case for Wildcard types `?`, `Any`, `AnyRef`, unconstrained type variables, or type variables constrained from above by an under-specified type. **Syntax** diff --git a/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala b/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala index a4a868202fe2..4026d5731278 100644 --- a/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala +++ b/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala @@ -20,10 +20,11 @@ import reflect.ClassTag @experimental object ExpressibleAsCollectionLiteral: // Some instances for standard collections. It would be good to have a method - // that works for all collections in stdlib. But to do that I believe we either - // have to put a given instance in Factory in stdlib, or write some macro - // method here. I have not found a straightforward way to build a collection - // of type `C` is all we know is the type. + // that works for all collections in stdlib. But to do that int his file, + // we have to write some macro method here. I have not found a straightforward + // way to build a collection of type `C` if all we know is the type. + // Once we can put Scala 3 code in the standard library this would be resolved by + // adding a given instance in Factory. given seqFromLiteral: [T] => ExpressibleAsCollectionLiteral[collection.Seq[T]]: type Elem = T From 17afcde5a7184e83e6ffdf9f3ade96d3161f541b Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 27 Nov 2024 11:46:55 +0100 Subject: [PATCH 13/15] Avoid repl crash and document that we need 3.7 --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 5 +++-- docs/_docs/reference/experimental/collection-literals.md | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index cb3d62a598ef..5c51422c2c4b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2747,8 +2747,9 @@ object Parsers { canApply = false blockExpr() case LBRACKET if in.featureEnabled(Feature.collectionLiterals) => - inBrackets: - SeqLiteral(exprsInBrackets(), TypeTree()) + atSpan(in.offset): + inBrackets: + SeqLiteral(exprsInBrackets(), TypeTree()) case QUOTE => quote(location.inPattern) case NEW => diff --git a/docs/_docs/reference/experimental/collection-literals.md b/docs/_docs/reference/experimental/collection-literals.md index 95dd225d0fed..3075dd623c45 100644 --- a/docs/_docs/reference/experimental/collection-literals.md +++ b/docs/_docs/reference/experimental/collection-literals.md @@ -10,6 +10,10 @@ Support for collection literals is enabled by the experimental language import ```scala import scala.language.experimental.collectionLiterals ``` +This feature requires a source version 3.7 or higher. One can specify both import and source version on the command line with these settings: +``` + -source 3.7 -language:experimental.collectionLiterals +``` Collection literals are comma-separated sequences of expressions, like these: ```scala val oneTwoThree = [1, 2, 3] From 212099699776c464272ce2bd3da86de3ca69cce5 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 27 Nov 2024 20:29:04 +0100 Subject: [PATCH 14/15] Update check file --- tests/neg/seqlits.check | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/neg/seqlits.check b/tests/neg/seqlits.check index 67e9c0e6120f..b7da59a970b6 100644 --- a/tests/neg/seqlits.check +++ b/tests/neg/seqlits.check @@ -1,31 +1,31 @@ --- [E172] Type Error: tests/neg/seqlits.scala:23:14 -------------------------------------------------------------------- +-- [E172] Type Error: tests/neg/seqlits.scala:23:13 -------------------------------------------------------------------- 23 | val x: A = [1, 2, 3] // error: ambiguous - | ^^^^^^^ + | ^^^^^^^^^ |Ambiguous given instances: both given instance given_ExpressibleAsCollectionLiteral_B in object SeqLits and given instance given_ExpressibleAsCollectionLiteral_C in object SeqLits match type scala.compiletime.ExpressibleAsCollectionLiteral[A] --- [E007] Type Mismatch Error: tests/neg/seqlits.scala:24:14 ----------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg/seqlits.scala:24:13 ----------------------------------------------------------- 24 | val y: D = [1, 2, 3] // error: type mismatch - | ^^^^^^^ - | Found: Seq[Int] - | Required: D + | ^^^^^^^^^ + | Found: Seq[Int] + | Required: D | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/seqlits.scala:26:39 ----------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg/seqlits.scala:26:38 ----------------------------------------------------------- 26 | val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: type mismatch // error // error - | ^^^^^^^^ - | Found: (Seq[Int], Seq[Int]) - | Required: (scala.collection.immutable.BitSet, Seq[Int]) + | ^^^^^^^^^^ + | Found: (Seq[Int], Seq[Int]) + | Required: (scala.collection.immutable.BitSet, Seq[Int]) | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/seqlits.scala:26:51 ----------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg/seqlits.scala:26:50 ----------------------------------------------------------- 26 | val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: type mismatch // error // error - | ^^^^^^^^^^^^^^ - | Found: (Seq[Int], Seq[Int]) - | Required: (scala.collection.immutable.BitSet, Seq[Int]) + | ^^^^^^^^^^^^^^^^ + | Found: (Seq[Int], Seq[Int]) + | Required: (scala.collection.immutable.BitSet, Seq[Int]) | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/seqlits.scala:26:69 ----------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg/seqlits.scala:26:68 ----------------------------------------------------------- 26 | val mbss: Map[BitSet, Seq[Int]] = [[1] -> [1], [0, 2] -> [1, 2], [0] -> []] // error: type mismatch // error // error - | ^^^^^ + | ^^^^^^^^^ | Found: (Seq[Int], Seq[Nothing]) | Required: (scala.collection.immutable.BitSet, Seq[Int]) | From 4b8a83256439a86589f8e89b8193f45e805f507e Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 17 Jan 2025 16:00:31 +0100 Subject: [PATCH 15/15] Tweak to type inference of Seq and Array collection literals --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 7 ++++++- docs/_docs/reference/experimental/collection-literals.md | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8a5c6ce42ac4..9a87032ac9d9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2460,8 +2460,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer untpd.TypedSplice(witness.select(nme.fromLiteral)) case _ => defaultMaker + // When expected type is a Seq or Array, propagate the `elemProto` as expected + // type of the elements. + val elems = elemProto match + case WildcardType(_) => tree.elems + case _ => tree.elems.map(untpd.Typed(_, untpd.TypeTree(elemProto))) typed( - untpd.Apply(maker, tree.elems).withSpan(tree.span) + untpd.Apply(maker, elems).withSpan(tree.span) .showing(i"typed collection literal $tree ---> $result", typr) , pt) diff --git a/docs/_docs/reference/experimental/collection-literals.md b/docs/_docs/reference/experimental/collection-literals.md index 3075dd623c45..2196d5794741 100644 --- a/docs/_docs/reference/experimental/collection-literals.md +++ b/docs/_docs/reference/experimental/collection-literals.md @@ -82,6 +82,12 @@ Using this scheme, the literals we have seen earlier could also be given alterna implicit search. That is, an implicit search for a given of the type would not be attempted because the type is not specific enough. Concretely, this is the case for Wildcard types `?`, `Any`, `AnyRef`, unconstrained type variables, or type variables constrained from above by an under-specified type. + - If the expected type is a subtype of `Seq` or an array type, we typecheck the + elements with the elements of the expected type. This means we can get the same + precision in propagated expected types as if the constructor was written explicitly. + Hence, we can't regress by going from `Seq(...)` or `Array(...)` to a + collection literal. + **Syntax** ```