Skip to content

Commit b2b6ce4

Browse files
committed
Implement collection literals
1 parent 2ccabc0 commit b2b6ce4

File tree

14 files changed

+228
-61
lines changed

14 files changed

+228
-61
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ class Definitions {
563563
@tu lazy val Seq_lengthCompare: Symbol = SeqClass.requiredMethod(nme.lengthCompare, List(IntType))
564564
@tu lazy val Seq_length : Symbol = SeqClass.requiredMethod(nme.length)
565565
@tu lazy val Seq_toSeq : Symbol = SeqClass.requiredMethod(nme.toSeq)
566-
566+
@tu lazy val MapModule: Symbol = requiredModule("scala.collection.immutable.Map")
567567

568568
@tu lazy val StringOps: Symbol = requiredClass("scala.collection.StringOps")
569569
@tu lazy val StringOps_format: Symbol = StringOps.requiredMethod(nme.format)
@@ -582,6 +582,9 @@ class Definitions {
582582
@tu lazy val IArrayModule: Symbol = requiredModule("scala.IArray")
583583
def IArrayModuleClass: Symbol = IArrayModule.moduleClass
584584

585+
@tu lazy val ExpressibleAsCollectionLiteralClass: ClassSymbol = requiredClass("scala.compiletime.ExpressibleAsCollectionLiteral")
586+
@tu lazy val ExpressibleACL_fromLiteral: Symbol = ExpressibleAsCollectionLiteralClass.requiredMethod("fromLiteral")
587+
585588
@tu lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", java.lang.Void.TYPE, UnitEnc, nme.specializedTypeNames.Void)
586589
def UnitClass(using Context): ClassSymbol = UnitType.symbol.asClass
587590
def UnitModuleClass(using Context): Symbol = UnitType.symbol.asClass.linkedClass

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ object StdNames {
500500
val foreach: N = "foreach"
501501
val format: N = "format"
502502
val fromDigits: N = "fromDigits"
503+
val fromLiteral: N = "fromLiteral"
503504
val fromProduct: N = "fromProduct"
504505
val genericArrayOps: N = "genericArrayOps"
505506
val genericClass: N = "genericClass"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4913,7 +4913,7 @@ object Types extends TypeUtils {
49134913
/** The state owning the variable. This is at first `creatorState`, but it can
49144914
* be changed to an enclosing state on a commit.
49154915
*/
4916-
private[core] var owningState: WeakReference[TyperState] | Null =
4916+
private[dotc] var owningState: WeakReference[TyperState] | Null =
49174917
if (creatorState == null) null else new WeakReference(creatorState)
49184918

49194919
/** The nesting level of this type variable in the current typer state. This is usually

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2390,7 +2390,7 @@ object Parsers {
23902390
in.token match
23912391
case IMPLICIT =>
23922392
closure(start, location, modifiers(BitSet(IMPLICIT)))
2393-
case LBRACKET =>
2393+
case LBRACKET if followingIsArrow() =>
23942394
val start = in.offset
23952395
val tparams = typeParamClause(ParamOwner.Type)
23962396
val arrowOffset = accept(ARROW)
@@ -2710,6 +2710,7 @@ object Parsers {
27102710
* | xmlLiteral
27112711
* | SimpleRef
27122712
* | `(` [ExprsInParens] `)`
2713+
* | `[` ExprsInBrackets `]`
27132714
* | SimpleExpr `.` id
27142715
* | SimpleExpr `.` MatchClause
27152716
* | SimpleExpr (TypeArgs | NamedTypeArgs)
@@ -2745,6 +2746,9 @@ object Parsers {
27452746
case LBRACE | INDENT =>
27462747
canApply = false
27472748
blockExpr()
2749+
case LBRACKET =>
2750+
inBrackets:
2751+
SeqLiteral(exprsInBrackets(), TypeTree())
27482752
case QUOTE =>
27492753
quote(location.inPattern)
27502754
case NEW =>
@@ -2840,6 +2844,12 @@ object Parsers {
28402844
commaSeparatedRest(exprOrBinding(), exprOrBinding)
28412845
}
28422846

2847+
/** ExprsInBrackets ::= ExprInParens {`,' ExprInParens} */
2848+
def exprsInBrackets(): List[Tree] =
2849+
if in.token == RBRACKET then Nil
2850+
else in.currentRegion.withCommasExpected:
2851+
commaSeparatedRest(exprInParens(), exprInParens)
2852+
28432853
/** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)'
28442854
* | `(' [ExprsInParens `,'] PostfixExpr `*' ')'
28452855
*/

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,7 @@ object Erasure {
891891
// The following four methods take as the proto-type the erasure of the pre-existing type,
892892
// if the original proto-type is not a value type.
893893
// This makes all branches be adapted to the correct type.
894-
override def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): SeqLiteral =
894+
override def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): Tree =
895895
super.typedSeqLiteral(tree, erasure(tree.typeOpt))
896896
// proto type of typed seq literal is original type;
897897

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,10 +443,12 @@ object Implicits:
443443
extends SearchResult with RefAndLevel with Showable:
444444
final def found = ref :: Nil
445445

446+
def isAmbiguousGiven(tree: Tree) = tree.tpe.isInstanceOf[AmbiguousImplicits | TooUnspecific]
447+
446448
/** A failed search */
447449
case class SearchFailure(tree: Tree) extends SearchResult {
448450
require(tree.tpe.isInstanceOf[SearchFailureType], s"unexpected type for ${tree}")
449-
final def isAmbiguous: Boolean = tree.tpe.isInstanceOf[AmbiguousImplicits | TooUnspecific]
451+
final def isAmbiguous: Boolean = isAmbiguousGiven(tree)
450452
final def reason: SearchFailureType = tree.tpe.asInstanceOf[SearchFailureType]
451453
final def found = tree.tpe match
452454
case tpe: AmbiguousImplicits => tpe.alt1.ref :: tpe.alt2.ref :: Nil

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@ object Inferencing {
5858
* The method is called to instantiate type variables before an implicit search.
5959
*/
6060
def instantiateSelected(tp: Type, tvars: List[Type])(using Context): Unit =
61-
if (tvars.nonEmpty)
62-
IsFullyDefinedAccumulator(
61+
if tvars.nonEmpty then instantiateSelected(tp, tvars.contains, minimize = true)
62+
63+
def instantiateSelected(tp: Type, cond: TypeVar => Boolean, minimize: Boolean)(using Context): Unit =
64+
IsFullyDefinedAccumulator(
6365
new ForceDegree.Value(IfBottom.flip):
64-
override def appliesTo(tvar: TypeVar) = tvars.contains(tvar),
65-
minimizeSelected = true
66+
override def appliesTo(tvar: TypeVar) = cond(tvar),
67+
minimizeSelected = minimize
6668
).process(tp)
6769

6870
/** Instantiate any type variables in `tp` whose bounds contain a reference to

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

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2375,7 +2375,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
23752375
Annotation(defn.RequiresCapabilityAnnot, cap, tree.span))))
23762376
else res
23772377

2378-
def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): SeqLiteral = {
2378+
def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): Tree =
23792379
val elemProto = pt.stripNull().elemType match {
23802380
case NoType => WildcardType
23812381
case bounds: TypeBounds => WildcardType(bounds)
@@ -2385,25 +2385,78 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
23852385
def assign(elems1: List[Tree], elemtpt1: Tree) =
23862386
assignType(cpy.SeqLiteral(tree)(elems1, elemtpt1), elems1, elemtpt1)
23872387

2388-
if (!tree.elemtpt.isEmpty) {
2388+
// Seq literal used in varargs: elem type is given
2389+
def varargSeqLiteral =
23892390
val elemtpt1 = typed(tree.elemtpt, elemProto)
23902391
val elems1 = tree.elems.mapconserve(typed(_, elemtpt1.tpe))
23912392
assign(elems1, elemtpt1)
2392-
}
2393-
else {
2394-
assert(tree.isInstanceOf[Trees.JavaSeqLiteral[?]])
2393+
2394+
// Seq literal used in Java annotations: elem type needs to be computed
2395+
def javaSeqLiteral =
23952396
val elems1 = tree.elems.mapconserve(typed(_, elemProto))
23962397
val elemtptType =
2397-
if (isFullyDefined(elemProto, ForceDegree.none))
2398+
if isFullyDefined(elemProto, ForceDegree.none) then
23982399
elemProto
2399-
else if (tree.elems.isEmpty && tree.isInstanceOf[Trees.JavaSeqLiteral[?]])
2400+
else if tree.elems.isEmpty then
24002401
defn.ObjectType // generic empty Java varargs are of type Object[]
24012402
else
24022403
TypeComparer.lub(elems1.tpes)
24032404
val elemtpt1 = typed(tree.elemtpt, elemtptType)
24042405
assign(elems1, elemtpt1)
2405-
}
2406-
}
2406+
2407+
// Stand-alone collection literal [x1, ..., xN]
2408+
def collectionLiteral =
2409+
def isArrow(tree: untpd.Tree) = tree match
2410+
case untpd.InfixOp(_, Ident(nme.PUREARROW), _) => true
2411+
case _ => false
2412+
2413+
// The default maker if no typeclass is searched or found
2414+
def defaultMaker =
2415+
if tree.elems.nonEmpty && tree.elems.forall(isArrow)
2416+
then untpd.ref(defn.MapModule)
2417+
else untpd.ref(defn.SeqModule)
2418+
2419+
// We construct and typecheck a term `maker(tree.elems)`, where `maker`
2420+
// is either a given instance of type ExpressibleAsCollectionLiteralClass
2421+
// or a default instance. The default instance is either Seq or Map,
2422+
// depending on the forms of `tree.elems`. We search for a type class if
2423+
// the expected type is a value type that is not an uninstantiated type variable.
2424+
val maker = pt match
2425+
case pt: TypeVar if !pt.isInstantiated =>
2426+
defaultMaker
2427+
case pt: ValueType =>
2428+
val tc = defn.ExpressibleAsCollectionLiteralClass.typeRef.appliedTo(pt)
2429+
val nestedCtx = ctx.fresh.setNewTyperState()
2430+
val maker = inContext(nestedCtx):
2431+
// Find given instance `witness` of type `ExpressibleAsCollectionLiteral[<pt>]`
2432+
val witness = inferImplicitArg(tc, tree.span.startPos)
2433+
if witness.tpe.isInstanceOf[SearchFailureType] then
2434+
val msg = missingArgMsg(witness, pt, "")
2435+
if isAmbiguousGiven(witness) then report.error(msg, tree.srcPos)
2436+
else typr.println(i"failed collection literal witness: ${msg.toString}")
2437+
defaultMaker
2438+
else
2439+
// Instantiate local type variables in witness.tpe, so that nested
2440+
// SeqLiterals don't get typed as default Seq due to first case above
2441+
def isLocal(tv: TypeVar) =
2442+
val state = tv.owningState
2443+
state != null && (state.get eq ctx.typerState)
2444+
instantiateSelected(witness.tpe, isLocal, minimize = false)
2445+
// Continue with typing `witness.fromLiteral` as the constructor
2446+
untpd.TypedSplice(witness.select(nme.fromLiteral))
2447+
nestedCtx.typerState.commit()
2448+
maker
2449+
case _ =>
2450+
defaultMaker
2451+
typed(
2452+
untpd.Apply(maker, tree.elems).withSpan(tree.span)
2453+
.showing(i"typed collection literal $tree ---> $result", typr)
2454+
, pt)
2455+
2456+
if !tree.elemtpt.isEmpty then varargSeqLiteral
2457+
else if tree.isInstanceOf[Trees.JavaSeqLiteral[?]] then javaSeqLiteral
2458+
else collectionLiteral
2459+
end typedSeqLiteral
24072460

24082461
def typedInlined(tree: untpd.Inlined, pt: Type)(using Context): Tree =
24092462
throw new UnsupportedOperationException("cannot type check a Inlined node")

docs/_docs/internals/syntax.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ SimpleExpr ::= SimpleRef
278278
| ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody] New(constr | templ)
279279
| ‘new’ TemplateBody
280280
| ‘(’ ExprsInParens ‘)’ Parens(exprs)
281+
| ‘[’ ExprInBrackets ‘)’ SeqLiteral(exprs, TypeTree())
281282
| SimpleExpr ‘.’ id Select(expr, id)
282283
| SimpleExpr ‘.’ MatchClause
283284
| SimpleExpr TypeArgs TypeApply(expr, args)
@@ -299,6 +300,7 @@ TypeSplice ::= spliceId
299300
| ‘$’ ‘{’ Pattern ‘}’ -- when inside quoted type pattern -- deprecated syntax
300301
ExprsInParens ::= ExprInParens {‘,’ ExprInParens}
301302
| NamedExprInParens {‘,’ NamedExprInParens}
303+
ExprsInBrackets ::= ExprInParens {‘,’ ExprInParens}
302304
ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here
303305
| Expr
304306
NamedExprInParens ::= id '=' ExprInParens
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package scala.compiletime
2+
import reflect.ClassTag
3+
4+
// This is in compiletime since it is an inline type class. Another
5+
// possibility would be to put it in `scala.collection`
6+
7+
/** A typeclass that supports creating collection-like data from
8+
* collection literals `[x1,...,xN]`.
9+
*/
10+
trait ExpressibleAsCollectionLiteral[+Coll]:
11+
12+
/** The element type of the created collection */
13+
type Elem
14+
15+
/** The inline method that creates the collection */
16+
inline def fromLiteral(inline xs: Elem*): Coll
17+
18+
object ExpressibleAsCollectionLiteral:
19+
20+
// Some instances for standard collections. It would be good to have a method
21+
// that works for all collections in stdlib. But to do that I believe we either
22+
// have to put a given instance in Factory in stdlib, or write some macro
23+
// method here. I have not found a straightforward way to build a collection
24+
// of type `C` is all we know is the type.
25+
26+
given seqFromLiteral: [T] => ExpressibleAsCollectionLiteral[collection.Seq[T]]:
27+
type Elem = T
28+
inline def fromLiteral(inline xs: T*): Seq[T] = Seq(xs*)
29+
30+
given mutableSeqFromLiteral: [T] => ExpressibleAsCollectionLiteral[collection.mutable.Seq[T]]:
31+
type Elem = T
32+
inline def fromLiteral(inline xs: T*) = collection.mutable.Seq(xs*)
33+
34+
given immutableSeqFromLiteral: [T] => ExpressibleAsCollectionLiteral[collection.immutable.Seq[T]]:
35+
type Elem = T
36+
inline def fromLiteral(inline xs: T*) = collection.immutable.Seq(xs*)
37+
38+
given vectorFromLiteral: [T] => ExpressibleAsCollectionLiteral[Vector[T]]:
39+
type Elem = T
40+
inline def fromLiteral(inline xs: T*) = Vector[Elem](xs*)
41+
42+
given listFromLiteral: [T] => ExpressibleAsCollectionLiteral[List[T]]:
43+
type Elem = T
44+
inline def fromLiteral(inline xs: T*) = List(xs*)
45+
46+
given arrayFromLiteral: [T: ClassTag] => ExpressibleAsCollectionLiteral[Array[T]]:
47+
type Elem = T
48+
inline def fromLiteral(inline xs: T*) = Array(xs*)
49+
50+
given iarrayFromLiteral: [T: ClassTag] => ExpressibleAsCollectionLiteral[IArray[T]]:
51+
type Elem = T
52+
inline def fromLiteral(inline xs: T*) = IArray(xs*)
53+
54+
given bitsetFromLiteral: ExpressibleAsCollectionLiteral[collection.immutable.BitSet]:
55+
type Elem = Int
56+
inline def fromLiteral(inline xs: Int*) = collection.immutable.BitSet(xs*)

0 commit comments

Comments
 (0)