Skip to content

Add named params - first iteration #1019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
08fe407
Remove unnecessary logic in toBounds
odersky Jan 4, 2016
a0b6c8e
Support named type parameters
odersky Jan 7, 2016
9c3b6fa
First version of named type arguments
odersky Jan 9, 2016
f20f2a0
Make some operations surivive partial named parameter lists
odersky Jan 10, 2016
79409ee
Augment test case
odersky Jan 10, 2016
312c982
Make named parameter alias handling more robust
odersky Jan 10, 2016
2475561
Avoid cyclic reference error when building dotty.
odersky Jan 10, 2016
577a0f1
Move failing test to pending
odersky Jan 10, 2016
08f7b36
Fix pending test for pickling
odersky Jan 11, 2016
8fa5a1f
Fix PostTyper normalization for named args
odersky Jan 12, 2016
d3a5d37
Check named type params for welformedness rules.
odersky Jan 12, 2016
9d08a17
Annotate test with // error indications
odersky Jan 12, 2016
a6edb4f
More named param tests
odersky Jan 12, 2016
511a4e8
Address reviewer comments
odersky Jan 17, 2016
19b702d
Add doc comment
odersky Jan 18, 2016
75d1237
Make type parameter reordering generally available.
odersky Jan 18, 2016
eead545
Add test to illustrate overloading problem.
odersky Jan 18, 2016
a6b7326
Fix assertion error message
odersky Jan 18, 2016
5fad3df
Fix review comment in previous PR
odersky Jan 18, 2016
ae38b6d
Fix problem of overloading resolution when receiver is not stable.
odersky Jan 18, 2016
b9ff5b7
Take defult parameters into account for overloading resolution.
odersky Jan 19, 2016
2334164
Add some flexibility in comparing named and unnamed parameterized types.
odersky Jan 20, 2016
91c846b
Use hasNamedArgs instead of repeating test inline.
odersky Jan 24, 2016
ea80431
Allow Named Arguments in TypeArgs
odersky Jan 29, 2016
90f8a22
Simplify logic in matchNamed
odersky Feb 9, 2016
c534a1b
Address reviewer comments
odersky Feb 12, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/SyntaxSummary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ grammar.
RefinedType ::= WithType {[nl] Refinement} RefinedTypeTree(t, ds)
WithType ::= AnnotType {`with' AnnotType} (deprecated)
AnnotType ::= SimpleType {Annotation} Annotated(t, annot)
SimpleType ::= SimpleType TypeArgs AppliedTypeTree(t, args)
SimpleType ::= SimpleType (TypeArgs | NamedTypeArgs) AppliedTypeTree(t, args)
| SimpleType `#' id SelectFromTypeTree(t, name)
| StableId
| Path `.' `type' SingletonTypeTree(p)
Expand All @@ -118,6 +118,8 @@ grammar.
ParamType ::= [`=>'] ParamValueType
ParamValueType ::= Type [`*'] PostfixOp(t, "*")
TypeArgs ::= `[' ArgTypes `]' ts
NamedTypeArg ::= id `=' ArgType NamedArg(id, t)
NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]' nts
Refinement ::= `{' [Dcl] {semi [Dcl]} `}' ds
TypeBounds ::= [`>:' Type] [`<: Type] | INT TypeBoundsTree(lo, hi)
TypeParamBounds ::= TypeBounds {`<%' Type} {`:' Type} ContextBounds(typeBounds, tps)
Expand Down Expand Up @@ -160,7 +162,7 @@ grammar.
| `_'
| `(' ExprsInParens `)' Parens(exprs)
| SimpleExpr `.' id Select(expr, id)
| SimpleExpr TypeArgs TypeApply(expr, args)
| SimpleExpr (TypeArgs | NamedTypeArgs) TypeApply(expr, args)
| SimpleExpr1 ArgumentExprs Apply(expr, args)
| XmlExpr
ExprsInParens ::= ExprInParens {`,' ExprInParens}
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ object desugar {
val defctx = ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next
var local = defctx.denotNamed(tp.name).suchThat(_ is ParamOrAccessor).symbol
if (local.exists) (defctx.owner.thisType select local).dealias
else throw new Error(s"no matching symbol for ${sym.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}")
else throw new Error(s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}")
case _ =>
mapOver(tp)
}
Expand Down
4 changes: 4 additions & 0 deletions src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
case _ => false
}

/** Does this list contain a named argument tree? */
def hasNamedArg(args: List[Any]) = args exists isNamedArg
val isNamedArg = (arg: Any) => arg.isInstanceOf[Trees.NamedArg[_]]

/** Is this pattern node a catch-all (wildcard or variable) pattern? */
def isDefaultCase(cdef: CaseDef) = cdef match {
case CaseDef(pat, EmptyTree, _) => isWildcardArg(pat)
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def Typed(expr: Tree, tpt: Tree)(implicit ctx: Context): Typed =
ta.assignType(untpd.Typed(expr, tpt), tpt)

def NamedArg(name: Name, arg: Tree)(implicit ctx: Context) =
def NamedArg(name: Name, arg: Tree)(implicit ctx: Context): NamedArg =
ta.assignType(untpd.NamedArg(name, arg), arg)

def Assign(lhs: Tree, rhs: Tree)(implicit ctx: Context): Assign =
Expand Down
3 changes: 3 additions & 0 deletions src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,9 @@ object Flags {
/** A private parameter accessor */
final val PrivateParamAccessor = allOf(Private, ParamAccessor)

/** A type parameter introduced with [type ... ] */
final val NamedTypeParam = allOf(TypeParam, ParamAccessor)

/** A local parameter */
final val ParamAndLocal = allOf(Param, Local)

Expand Down
14 changes: 14 additions & 0 deletions src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,20 @@ object SymDenotations {
if (myMemberCache != null) myMemberCache invalidate sym.name
}

/** Make sure the type parameters of this class are `tparams`, reorder definitions
* in scope if necessary.
* @pre All type parameters in `tparams` are entered in class scope `info.decls`.
*/
def updateTypeParams(tparams: List[Symbol])(implicit ctx: Context): Unit =
if (!typeParams.corresponds(tparams)(_.name == _.name)) {
val decls = info.decls
val decls1 = newScope
for (tparam <- tparams) decls1.enter(decls.lookup(tparam.name))
for (sym <- decls) if (!typeParams.contains(sym)) decls1.enter(sym)
info = classInfo.derivedClassInfo(decls = decls1)
myTypeParams = null
}

/** All members of this class that have the given name.
* The elements of the returned pre-denotation all
* have existing symbols.
Expand Down
8 changes: 5 additions & 3 deletions src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,7 @@ class TypeApplications(val self: Type) extends AnyVal {
else tsym.infoOrCompleter match {
case completer: TypeParamsCompleter =>
val tparams = completer.completerTypeParams(tsym)
if (tsym.isClass) tparams
else defn.LambdaTrait(tparams.map(_.variance)).typeParams
defn.LambdaTrait(tparams.map(_.variance)).typeParams
case _ =>
if (!tsym.isCompleting || tsym.isAliasType) tsym.info.typeParams
else
Expand Down Expand Up @@ -548,9 +547,12 @@ class TypeApplications(val self: Type) extends AnyVal {
self
case _ =>
val v = tparam.variance
/* Not neeeded.
if (v > 0 && !(tparam is Local) && !(tparam is ExpandedTypeParam)) TypeBounds.upper(self)
else if (v < 0 && !(tparam is Local) && !(tparam is ExpandedTypeParam)) TypeBounds.lower(self)
else TypeAlias(self, v)
else
*/
TypeAlias(self, v)
}

/** The type arguments of this type's base type instance wrt. `base`.
Expand Down
41 changes: 38 additions & 3 deletions src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
case _ =>
compareRefinedSlow ||
fourthTry(tp1, tp2) ||
compareHkLambda(tp2, tp1, inOrder = false)
compareHkLambda(tp2, tp1, inOrder = false) ||
compareAliasedRefined(tp2, tp1, inOrder = false)
}
else // fast path, in particular for refinements resulting from parameterization.
isSubType(tp1, skipped2) &&
Expand Down Expand Up @@ -491,7 +492,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
}
isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths
case tp1: RefinedType =>
isNewSubType(tp1.parent, tp2) || compareHkLambda(tp1, tp2, inOrder = true)
isNewSubType(tp1.parent, tp2) ||
compareHkLambda(tp1, tp2, inOrder = true) ||
compareAliasedRefined(tp1, tp2, inOrder = true)
case AndType(tp11, tp12) =>
// Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2
// and analogously for T11 & (T121 | T122) & T12 <: T2
Expand Down Expand Up @@ -614,6 +617,35 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
false
}

/** Say we are comparing a refined type `P{type M = U}` or `P{type M >: L <: U}`.
* If P#M refers to a BaseTypeArg aliased to some other typeref P#N,
* do the same comparison with `P{type N = U}` or `P{type N >: L <: U}`, respectively.
* This allows to handle situations involving named type params like this one:
*
* trait Lambda[type Elem]
* trait Lst[T] extends Lambda[T]
*
* compareAliasedRefined is necessary so we establish that
*
* Lst[Int] = Lst[Elem = Int]
*/
private def compareAliasedRefined(rt: RefinedType, other: Type, inOrder: Boolean) = {
val mbr = refinedSymbol(rt)
mbr.is(BaseTypeArg) && {
mbr.info match {
case TypeAlias(TypeRef(_, aliasName)) =>
val rt1 = rt.derivedRefinedType(rt.parent, aliasName, rt.refinedInfo)
subtyping.println(i"rewiring $rt to $rt1 in comparison with $other")
if (inOrder) isSubType(rt1, other) else isSubType(other, rt1)
case _ =>
false
}
}
}

/** The symbol referred to in the refinement of `rt` */
private def refinedSymbol(rt: RefinedType) = rt.parent.member(rt.refinedName).symbol

/** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time
* to keep the constraint as wide as possible. Specifically, if
*
Expand Down Expand Up @@ -742,11 +774,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
/** A type has been covered previously in subtype checking if it
* is some combination of TypeRefs that point to classes, where the
* combiners are RefinedTypes, AndTypes or AnnotatedTypes.
* One exception: Refinements referring to basetype args are never considered
* to be already covered. This is necessary because such refined types might
* still need to be compared with a compareAliasRefined.
*/
private def isCovered(tp: Type): Boolean = tp.dealias.stripTypeVar match {
case tp: TypeRef => tp.symbol.isClass && tp.symbol != NothingClass && tp.symbol != NullClass
case tp: ProtoType => false
case tp: RefinedType => isCovered(tp.parent)
case tp: RefinedType => isCovered(tp.parent) && !refinedSymbol(tp).is(BaseTypeArg)
case tp: AnnotatedType => isCovered(tp.underlying)
case AndType(tp1, tp2) => isCovered(tp1) && isCovered(tp2)
case _ => false
Expand Down
14 changes: 10 additions & 4 deletions src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -444,10 +444,16 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
case tp: TypeRef =>
tp
case tp @ RefinedType(tp1, name: TypeName) =>
val prevInfo = refinements(name)
refinements = refinements.updated(name,
if (prevInfo == null) tp.refinedInfo else prevInfo & tp.refinedInfo)
formals = formals.updated(name, tp1.typeParamNamed(name))
tp.refinedInfo match {
case TypeAlias(TypeRef(pre, name1)) if name1 == name && (pre =:= cls.thisType) =>
// Don't record refinements of the form X = this.X (These can arise using named parameters).
typr.println(s"dropping refinement $tp")
case _ =>
val prevInfo = refinements(name)
refinements = refinements.updated(name,
if (prevInfo == null) tp.refinedInfo else prevInfo & tp.refinedInfo)
formals = formals.updated(name, tp1.typeParamNamed(name))
}
normalizeToRef(tp1)
case ErrorType =>
defn.AnyType
Expand Down
16 changes: 10 additions & 6 deletions src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,13 @@ object Types {
}
def goThis(tp: ThisType) = {
val d = go(tp.underlying)
if (d.exists) d
if (d.exists)
if ((pre eq tp) && d.symbol.is(NamedTypeParam) && (d.symbol.owner eq tp.cls))
// If we look for a named type parameter `P` in `C.this.P`, looking up
// the fully applied self type of `C` will give as an info the alias type
// `P = this.P`. We need to return a denotation with the underlying bounds instead.
d.symbol.denot
else d
else
// There is a special case to handle:
// trait Super { this: Sub => private class Inner {} println(this.Inner) }
Expand Down Expand Up @@ -854,11 +860,9 @@ object Types {
else NoType
case tp: AnnotatedType => tp.underlying.underlyingClassRef(refinementOK)
case tp: RefinedType =>
if (refinementOK) tp.underlying.underlyingClassRef(refinementOK)
else {
val tycon = tp.withoutArgs(tp.argInfos)
if (tycon eq tp) NoType else tycon.underlyingClassRef(refinementOK)
}
def isParamName = tp.classSymbol.typeParams.exists(_.name == tp.refinedName)
if (refinementOK || isParamName) tp.underlying.underlyingClassRef(refinementOK)
else NoType
case _ => NoType
}

Expand Down
14 changes: 3 additions & 11 deletions src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,10 @@ object Scala2Unpickler {
} else {
registerCompanionPair(scalacCompanion, denot.classSymbol)
}
val declsTypeParams = denot.typeParams
val declsInRightOrder =
if (declsTypeParams.corresponds(tparams)(_.name == _.name)) decls
else { // create new scope with type parameters in right order
val decls1 = newScope
for (tparam <- tparams) decls1.enter(decls.lookup(tparam.name))
for (sym <- decls) if (!declsTypeParams.contains(sym)) decls1.enter(sym)
decls1
}

denot.info = ClassInfo( // final info
denot.owner.thisType, denot.classSymbol, parentRefs, declsInRightOrder, ost)
denot.info = ClassInfo( // final info, except possibly for typeparams ordering
denot.owner.thisType, denot.classSymbol, parentRefs, decls, ost)
denot.updateTypeParams(tparams)
}
}

Expand Down
45 changes: 37 additions & 8 deletions src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ object Parsers {

private def simpleTypeRest(t: Tree): Tree = in.token match {
case HASH => simpleTypeRest(typeProjection(t))
case LBRACKET => simpleTypeRest(atPos(t.pos.start) { AppliedTypeTree(t, typeArgs()) })
case LBRACKET => simpleTypeRest(atPos(t.pos.start) { AppliedTypeTree(t, typeArgs(namedOK = true)) })
case _ => t
}

Expand All @@ -759,9 +759,37 @@ object Parsers {
}
else typ()

/** NamedTypeArg ::= id `=' ArgType
*/
val namedTypeArg = () => {
val name = ident()
accept(EQUALS)
NamedArg(name.toTypeName, argType())
}

/** ArgTypes ::= ArgType {`,' ArgType}
* NamedTypeArg {`,' NamedTypeArg}
*/
def argTypes() = commaSeparated(argType)
def argTypes(namedOK: Boolean = false) = {
def otherArgs(first: Tree, arg: () => Tree): List[Tree] = {
val rest =
if (in.token == COMMA) {
in.nextToken()
commaSeparated(arg)
}
else Nil
first :: rest
}
if (namedOK && in.token == IDENTIFIER)
argType() match {
case Ident(name) if in.token == EQUALS =>
in.nextToken()
otherArgs(NamedArg(name, argType()), namedTypeArg)
case firstArg =>
otherArgs(firstArg, argType)
}
else commaSeparated(argType)
}

/** FunArgType ::= ArgType | `=>' ArgType
*/
Expand All @@ -785,9 +813,10 @@ object Parsers {
} else t
}

/** TypeArgs ::= `[' ArgType {`,' ArgType} `]'
*/
def typeArgs(): List[Tree] = inBrackets(argTypes())
/** TypeArgs ::= `[' ArgType {`,' ArgType} `]'
* NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]'
*/
def typeArgs(namedOK: Boolean = false): List[Tree] = inBrackets(argTypes(namedOK))

/** Refinement ::= `{' RefineStatSeq `}'
*/
Expand Down Expand Up @@ -1045,7 +1074,7 @@ object Parsers {
* | Path
* | `(' [ExprsInParens] `)'
* | SimpleExpr `.' Id
* | SimpleExpr TypeArgs
* | SimpleExpr (TypeArgs | NamedTypeArgs)
* | SimpleExpr1 ArgumentExprs
*/
def simpleExpr(): Tree = {
Expand Down Expand Up @@ -1094,7 +1123,7 @@ object Parsers {
in.nextToken()
simpleExprRest(selector(t), canApply = true)
case LBRACKET =>
val tapp = atPos(t.pos.start, in.offset) { TypeApply(t, typeArgs()) }
val tapp = atPos(t.pos.start, in.offset) { TypeApply(t, typeArgs(namedOK = true)) }
simpleExprRest(tapp, canApply = true)
case LPAREN | LBRACE if canApply =>
val app = atPos(t.pos.start, in.offset) { Apply(t, argumentExprs()) }
Expand Down Expand Up @@ -1493,7 +1522,7 @@ object Parsers {
atPos(modStart, in.offset) {
if (in.token == TYPE) {
in.nextToken()
mods | Param
mods | Param | ParamAccessor
} else {
if (mods.hasFlags) syntaxError("`type' expected")
mods | Param | PrivateLocal
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/transform/Constructors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor

// Drop accessors that are not retained from class scope
if (dropped.nonEmpty) {
val clsInfo = cls.classInfo // TODO investigate: expand clsInfo to cls.info => dotty type error
val clsInfo = cls.classInfo
cls.copy(
info = clsInfo.derivedClassInfo(
decls = clsInfo.decls.filteredScope(!dropped.contains(_))))
Expand Down
Loading