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/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2890bdf306be..41df762df26b 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,8 @@ 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 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/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7933cbbea12f..5c51422c2c4b 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,10 @@ object Parsers { case LBRACE | INDENT => canApply = false blockExpr() + case LBRACKET if in.featureEnabled(Feature.collectionLiterals) => + atSpan(in.offset): + inBrackets: + SeqLiteral(exprsInBrackets(), TypeTree()) case QUOTE => quote(location.inPattern) case NEW => @@ -2840,6 +2845,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..f668ab8148ea 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -616,6 +616,28 @@ 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) + + /** 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) + case tp: ViewProto => + isUnderspecified(tp.resType) + || tp.resType.isRef(defn.UnitClass) + || isUnderSpecifiedArgument(tp.argType.widen) + case _ => + tp.isAny || tp.isAnyRef end Implicits import Implicits.* @@ -1649,19 +1671,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/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 2ebcd96d5bde..3d963011a522 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -58,8 +58,7 @@ 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( + IsFullyDefinedAccumulator( new ForceDegree.Value(IfBottom.flip): override def appliesTo(tvar: TypeVar) = tvars.contains(tvar), minimizeSelected = true diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 76b853c4aabd..9a87032ac9d9 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,24 +2404,76 @@ 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 { + + // 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 underspeficied for implicit search. + val maker = pt match + case pt: ValueType if !Implicits.isUnderspecified(wildApprox(pt)) => + val tc = defn.ExpressibleAsCollectionLiteralClass.typeRef.appliedTo(pt) + val nestedCtx = ctx.fresh.setNewTyperState() + // 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 + 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)) + 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, 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..381eb08a3e8e 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) + | ‘[’ ExprInParens {‘,’ ExprInParens} ‘]’ SeqLiteral(exprs, TypeTree()) | SimpleExpr ‘.’ id Select(expr, id) | SimpleExpr ‘.’ MatchClause | SimpleExpr TypeArgs TypeApply(expr, args) diff --git a/docs/_docs/reference/experimental/collection-literals.md b/docs/_docs/reference/experimental/collection-literals.md new file mode 100644 index 000000000000..2196d5794741 --- /dev/null +++ b/docs/_docs/reference/experimental/collection-literals.md @@ -0,0 +1,96 @@ +--- +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 +``` +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] + 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 _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. + + - 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** + +``` +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/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala b/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala new file mode 100644 index 000000000000..4026d5731278 --- /dev/null +++ b/library/src/scala/compiletime/ExpressibleAsCollectionLiteral.scala @@ -0,0 +1,79 @@ +package scala.compiletime + +import annotation.experimental +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]`. + */ +@experimental 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 + +@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 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 + 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 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/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/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 diff --git a/tests/neg/seqlits.check b/tests/neg/seqlits.check new file mode 100644 index 000000000000..b7da59a970b6 --- /dev/null +++ b/tests/neg/seqlits.check @@ -0,0 +1,46 @@ +-- [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:13 ----------------------------------------------------------- +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: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]) + | + | longer explanation available when compiling with `-explain` +-- [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]) + | + | longer explanation available when compiling with `-explain` +-- [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]) + | + | 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 new file mode 100644 index 000000000000..c3166e925a61 --- /dev/null +++ b/tests/neg/seqlits.scala @@ -0,0 +1,36 @@ +import language.`3.7` +import compiletime.ExpressibleAsCollectionLiteral +import language.experimental.collectionLiterals +import collection.immutable.BitSet + +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 + + 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/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/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$", ) 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 new file mode 100644 index 000000000000..8601ed7aa559 --- /dev/null +++ b/tests/run/seqlits.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.scala b/tests/run/seqlits.scala new file mode 100644 index 000000000000..90ef36df07b8 --- /dev/null +++ b/tests/run/seqlits.scala @@ -0,0 +1,89 @@ +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) + +object SeqLits: + + 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 } + + 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] + 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], []] + 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 -> []] + // 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 + val x2 = f2([1, 2, 3]) + val _: Vector[Int] = x2 + + println(s"Seq $s") + println(s"Vector $v") + println(bs) + println(s"${t.getClass.getSimpleName} with elems ${t.body()}") + println(ve) + println(a.toList) + println(ia.toList) + println(iae.toList) + println(u) + println(e) + println(m) + println(ss) + println(ss2) + println(vs) + println(ab) + println(sbs) + 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"]