Skip to content

Commit 83ffe00

Browse files
Implement applied constructor types (#22543)
closes #22542 Introduce new syntax to make classes with `tracked` parameters easier to use. The new syntax is essentially the ability to use an application of a class constructor as a type, we call such types applied constructor types. With this new feature the following example compiles correctly and the type in the comment is the resulting type of the applied constructor types. ```scala import scala.language.experimental.modularity class C(tracked val v: Any) val c: C(42) /* C { val v: 42 } */ = C(42) ``` ### Syntax change ``` SimpleType ::= SimpleLiteral | ‘?’ TypeBounds --- | SimpleType1 +++ | SimpleType1 {ParArgumentExprs} ``` A `SimpleType` can now optionally be followed by `ParArgumentExprs`. --------- Co-authored-by: Matt Bovel <[email protected]>
1 parent 43118de commit 83ffe00

19 files changed

+206
-39
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,12 @@ object NamerOps:
3939
*/
4040
extension (tp: Type)
4141
def separateRefinements(cls: ClassSymbol, refinements: mutable.LinkedHashMap[Name, Type] | Null)(using Context): Type =
42-
val widenSkolemsMap = new TypeMap:
43-
def apply(tp: Type) = mapOver(tp.widenSkolem)
4442
tp match
4543
case RefinedType(tp1, rname, rinfo) =>
4644
try tp1.separateRefinements(cls, refinements)
4745
finally
4846
if refinements != null then
49-
val rinfo1 = widenSkolemsMap(rinfo)
47+
val rinfo1 = rinfo.widenSkolems
5048
refinements(rname) = refinements.get(rname) match
5149
case Some(tp) => tp & rinfo1
5250
case None => rinfo1

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ class TypeUtils:
5353
case ps => ps.reduceLeft(AndType(_, _))
5454
}
5555

56+
def widenSkolems(using Context): Type =
57+
val widenSkolemsMap = new TypeMap:
58+
def apply(tp: Type) = mapOver(tp.widenSkolem)
59+
widenSkolemsMap(self)
60+
5661
/** The element types of this tuple type, which can be made up of EmptyTuple, TupleX and `*:` pairs
5762
*/
5863
def tupleElementTypes(using Context): Option[List[Type]] =
@@ -134,7 +139,7 @@ class TypeUtils:
134139
case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.")
135140
val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil)
136141
names.zip(values)
137-
142+
138143
(if normalize then self.normalized else self).dealias match
139144
// for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply
140145
case defn.NamedTupleDirect(nmes, vals) => extractNamesTypes(nmes, vals)

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

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,16 +1331,6 @@ object Parsers {
13311331
*/
13321332
def qualId(): Tree = dotSelectors(termIdent())
13331333

1334-
/** Singleton ::= SimpleRef
1335-
* | SimpleLiteral
1336-
* | Singleton ‘.’ id
1337-
* -- not yet | Singleton ‘(’ Singletons ‘)’
1338-
* -- not yet | Singleton ‘[’ Types ‘]’
1339-
*/
1340-
def singleton(): Tree =
1341-
if isSimpleLiteral then simpleLiteral()
1342-
else dotSelectors(simpleRef())
1343-
13441334
/** SimpleLiteral ::= [‘-’] integerLiteral
13451335
* | [‘-’] floatingPointLiteral
13461336
* | booleanLiteral
@@ -2051,7 +2041,7 @@ object Parsers {
20512041
/** SimpleType ::= SimpleLiteral
20522042
* | ‘?’ TypeBounds
20532043
* | SimpleType1
2054-
* | SimpleType ‘(’ Singletons ‘)’ -- under language.experimental.dependent, checked in Typer
2044+
* | SimpleType ‘(’ Singletons ‘)’
20552045
* Singletons ::= Singleton {‘,’ Singleton}
20562046
*/
20572047
def simpleType(): Tree =
@@ -2083,11 +2073,11 @@ object Parsers {
20832073
val start = in.skipToken()
20842074
typeBounds().withSpan(Span(start, in.lastOffset, start))
20852075
else
2086-
def singletonArgs(t: Tree): Tree =
2087-
if in.token == LPAREN && in.featureEnabled(Feature.dependent)
2088-
then singletonArgs(AppliedTypeTree(t, inParensWithCommas(commaSeparated(singleton))))
2089-
else t
2090-
singletonArgs(simpleType1())
2076+
val tpt = simpleType1()
2077+
if in.featureEnabled(Feature.modularity) && in.token == LPAREN then
2078+
parArgumentExprss(wrapNew(tpt))
2079+
else
2080+
tpt
20912081

20922082
/** SimpleType1 ::= id
20932083
* | Singleton `.' id

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
225225
case FormatInterpolationErrorID // errorNumber: 209
226226
case ValueClassCannotExtendAliasOfAnyValID // errorNumber: 210
227227
case MatchIsNotPartialFunctionID // errorNumber: 211
228+
case OnlyFullyDependentAppliedConstructorTypeID // errorNumber: 212
229+
case PointlessAppliedConstructorTypeID // errorNumber: 213
228230

229231
def errorNumber = ordinal - 1
230232

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3487,3 +3487,24 @@ class MatchIsNotPartialFunction(using Context) extends SyntaxMsg(MatchIsNotParti
34873487
|
34883488
|Efficient operations will use `applyOrElse` to avoid computing the match twice,
34893489
|but the `apply` body would be executed "per element" in the example."""
3490+
3491+
final class PointlessAppliedConstructorType(tpt: untpd.Tree, args: List[untpd.Tree], tpe: Type)(using Context) extends TypeMsg(PointlessAppliedConstructorTypeID):
3492+
override protected def msg(using Context): String =
3493+
val act = i"$tpt(${args.map(_.show).mkString(", ")})"
3494+
i"""|Applied constructor type $act has no effect.
3495+
|The resulting type of $act is the same as its base type, namely: $tpe""".stripMargin
3496+
3497+
override protected def explain(using Context): String =
3498+
i"""|Applied constructor types are used to ascribe specialized types of constructor applications.
3499+
|To benefit from this feature, the constructor in question has to have a more specific type than the class itself.
3500+
|
3501+
|If you want to track a precise type of any of the class parameters, make sure to mark the parameter as `tracked`.
3502+
|Otherwise, you can safely remove the argument list from the type.
3503+
|"""
3504+
3505+
final class OnlyFullyDependentAppliedConstructorType()(using Context)
3506+
extends TypeMsg(OnlyFullyDependentAppliedConstructorTypeID):
3507+
override protected def msg(using Context): String =
3508+
i"Applied constructor type can only be used with classes where all parameters in the first parameter list are tracked"
3509+
3510+
override protected def explain(using Context): String = ""

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,10 @@ trait Applications extends Compatibility {
12821282
}
12831283
else {
12841284
val app = tree.fun match
1285+
case _ if ctx.mode.is(Mode.Type) && Feature.enabled(Feature.modularity) && !ctx.isAfterTyper =>
1286+
untpd.methPart(tree.fun) match
1287+
case Select(nw @ New(_), _) => typedAppliedConstructorType(nw, tree.args, tree)
1288+
case _ => realApply
12851289
case untpd.TypeApply(_: untpd.SplicePattern, _) if Feature.quotedPatternsWithPolymorphicFunctionsEnabled =>
12861290
typedAppliedSpliceWithTypes(tree, pt)
12871291
case _: untpd.SplicePattern => typedAppliedSplice(tree, pt)
@@ -1715,6 +1719,28 @@ trait Applications extends Compatibility {
17151719
def typedUnApply(tree: untpd.UnApply, selType: Type)(using Context): UnApply =
17161720
throw new UnsupportedOperationException("cannot type check an UnApply node")
17171721

1722+
/** Typecheck an applied constructor type – An Apply node in Type mode.
1723+
* This expands to the type this term would have if it were typed as an expression.
1724+
*
1725+
* e.g.
1726+
* ```scala
1727+
* // class C(tracked val v: Any)
1728+
* val c: C(42) = ???
1729+
* ```
1730+
*/
1731+
def typedAppliedConstructorType(nw: untpd.New, args: List[untpd.Tree], tree: untpd.Apply)(using Context) =
1732+
val tree1 = typedExpr(tree)
1733+
val preciseTp = tree1.tpe.widenSkolems
1734+
val classTp = typedType(nw.tpt).tpe
1735+
def classSymbolHasOnlyTrackedParameters =
1736+
!classTp.classSymbol.primaryConstructor.paramSymss.nestedExists: param =>
1737+
param.isTerm && !param.is(Tracked)
1738+
if !preciseTp.isError && !classSymbolHasOnlyTrackedParameters then
1739+
report.warning(OnlyFullyDependentAppliedConstructorType(), tree.srcPos)
1740+
if !preciseTp.isError && (preciseTp frozen_=:= classTp) then
1741+
report.warning(PointlessAppliedConstructorType(nw.tpt, args, classTp), tree.srcPos)
1742+
TypeTree(preciseTp)
1743+
17181744
/** Is given method reference applicable to argument trees `args`?
17191745
* @param resultType The expected result type of the application
17201746
*/

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ object ErrorReporting {
294294

295295
def dependentMsg =
296296
"""Term-dependent types are experimental,
297-
|they must be enabled with a `experimental.dependent` language import or setting""".stripMargin.toMessage
297+
|they must be enabled with a `experimental.modularity` language import or setting""".stripMargin.toMessage
298298

299299
def err(using Context): Errors = new Errors
300300
}

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

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2557,17 +2557,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
25572557
}
25582558

25592559
def typedAppliedTypeTree(tree: untpd.AppliedTypeTree)(using Context): Tree = {
2560-
tree.args match
2561-
case arg :: _ if arg.isTerm =>
2562-
if Feature.dependentEnabled then
2563-
return errorTree(tree, em"Not yet implemented: T(...)")
2564-
else
2565-
return errorTree(tree, dependentMsg)
2566-
case _ =>
2567-
2568-
val tpt1 = withoutMode(Mode.Pattern) {
2560+
val tpt1 = withoutMode(Mode.Pattern):
25692561
typed(tree.tpt, AnyTypeConstructorProto)
2570-
}
2562+
25712563
val tparams = tpt1.tpe.typeParams
25722564
if tpt1.tpe.isError then
25732565
val args1 = tree.args.mapconserve(typedType(_))
@@ -2691,7 +2683,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
26912683
typeIndexedLambdaTypeTree(tree, tparams, body)
26922684

26932685
def typedTermLambdaTypeTree(tree: untpd.TermLambdaTypeTree)(using Context): Tree =
2694-
if Feature.dependentEnabled then
2686+
if Feature.enabled(Feature.modularity) then
26952687
errorTree(tree, em"Not yet implemented: (...) =>> ...")
26962688
else
26972689
errorTree(tree, dependentMsg)

compiler/test/dotc/neg-best-effort-unpickling.excludelist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ i18750.scala
1818

1919
# Crash on invalid prefix ([A] =>> Int)
2020
i22357a.scala
21+
22+
# `110 (of class java.lang.Integer)`
23+
context-function-syntax.scala

docs/_docs/internals/syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ AnnotType1 ::= SimpleType1 {Annotation}
196196
197197
SimpleType ::= SimpleLiteral SingletonTypeTree(l)
198198
| ‘?’ TypeBounds
199-
| SimpleType1
199+
| SimpleType1 {ParArgumentExprs}
200200
SimpleType1 ::= id Ident(name)
201201
| Singleton ‘.’ id Select(t, name)
202202
| Singleton ‘.’ ‘type’ SingletonTypeTree(p)

0 commit comments

Comments
 (0)