Skip to content

Drop named type parameters in classes #2050

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

Merged
merged 3 commits into from
Mar 5, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
3 changes: 0 additions & 3 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,6 @@ trait ConstraintHandling {
if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound))
inst = ctx.harmonizeUnion(inst)

// 3. If instance is from below, and upper bound has open named parameters
// make sure the instance has all named parameters of the bound.
if (fromBelow) inst = inst.widenToNamedTypeParams(param.namedTypeParams)
inst
}

Expand Down
3 changes: 0 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -613,9 +613,6 @@ 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
17 changes: 1 addition & 16 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1086,9 +1086,6 @@ object SymDenotations {
/** The type parameters of a class symbol, Nil for all other symbols */
def typeParams(implicit ctx: Context): List[TypeSymbol] = Nil

/** The named type parameters declared or inherited by this symbol */
def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = Set()

/** The type This(cls), where cls is this class, NoPrefix for all other symbols */
def thisType(implicit ctx: Context): Type = NoPrefix

Expand Down Expand Up @@ -1226,11 +1223,9 @@ object SymDenotations {
/** TODO: Document why caches are supposedly safe to use */
private[this] var myTypeParams: List[TypeSymbol] = _

private[this] var myNamedTypeParams: Set[TypeSymbol] = _

/** The type parameters in this class, in the order they appear in the current
* scope `decls`. This might be temporarily the incorrect order when
* reading Scala2 pickled info. The problem is fixed by `updateTypeParams`
* reading Scala2 pickled info. The problem is fixed by `ensureTypeParamsInCorrectOrder`,
* which is called once an unpickled symbol has been completed.
*/
private def typeParamsFromDecls(implicit ctx: Context) =
Expand All @@ -1253,16 +1248,6 @@ object SymDenotations {
myTypeParams
}

/** The named type parameters declared or inherited by this class */
override final def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = {
def computeNamedTypeParams: Set[TypeSymbol] =
if (ctx.erasedTypes || is(Module)) Set() // fast return for modules to avoid scanning package decls
else memberNames(abstractTypeNameFilter).map(name =>
info.member(name).symbol.asType).filter(_.is(TypeParam, butNot = ExpandedName)).toSet
if (myNamedTypeParams == null) myNamedTypeParams = computeNamedTypeParams
myNamedTypeParams
}

override protected[dotc] final def info_=(tp: Type) = {
super.info_=(tp)
myTypeParams = null // changing the info might change decls, and with it typeParams
Expand Down
61 changes: 0 additions & 61 deletions compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -246,67 +246,6 @@ class TypeApplications(val self: Type) extends AnyVal {
case _ => Nil
}

/** The named type parameters declared or inherited by this type.
* These are all uninstantiated named type parameters of this type or one
* of its base types.
*/
final def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = self match {
case self: ClassInfo =>
self.cls.namedTypeParams
case self: RefinedType =>
self.parent.namedTypeParams.filterNot(_.name == self.refinedName)
case self: SingletonType =>
Set()
case self: TypeProxy =>
self.underlying.namedTypeParams
case _ =>
Set()
}

/** The smallest supertype of this type that instantiated none of the named type parameters
* in `params`. That is, for each named type parameter `p` in `params`, either there is
* no type field named `p` in this type, or `p` is a named type parameter of this type.
* The first case is important for the recursive case of AndTypes, because some of their operands might
* be missing the named parameter altogether, but the AndType as a whole can still
* contain it.
*/
final def widenToNamedTypeParams(params: Set[TypeSymbol])(implicit ctx: Context): Type = {

/** Is widening not needed for `tp`? */
def isOK(tp: Type) = {
val ownParams = tp.namedTypeParams
def isMissingOrOpen(param: TypeSymbol) = {
val ownParam = tp.nonPrivateMember(param.name).symbol
!ownParam.exists || ownParams.contains(ownParam.asType)
}
params.forall(isMissingOrOpen)
}

/** Widen type by forming the intersection of its widened parents */
def widenToParents(tp: Type) = {
val parents = tp.parents.map(p =>
tp.baseTypeWithArgs(p.symbol).widenToNamedTypeParams(params))
parents.reduceLeft(ctx.typeComparer.andType(_, _))
}

if (isOK(self)) self
else self match {
case self @ AppliedType(tycon, args) if !isOK(tycon) =>
widenToParents(self)
case self: TypeRef if self.symbol.isClass =>
widenToParents(self)
case self: RefinedType =>
val parent1 = self.parent.widenToNamedTypeParams(params)
if (params.exists(_.name == self.refinedName)) parent1
else self.derivedRefinedType(parent1, self.refinedName, self.refinedInfo)
case self: TypeProxy =>
self.superType.widenToNamedTypeParams(params)
case self: AndOrType =>
self.derivedAndOrType(
self.tp1.widenToNamedTypeParams(params), self.tp2.widenToNamedTypeParams(params))
}
}

/** Is self type higher-kinded (i.e. of kind != "*")? */
def isHK(implicit ctx: Context): Boolean = self.dealias match {
case self: TypeRef => self.info.isHK
Expand Down
14 changes: 1 addition & 13 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1264,22 +1264,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
}
}

/** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors with at least
* some unnamed type parameters.
/** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors.
* In the latter case, combine `tp1` and `tp2` under a type lambda like this:
*
* [X1, ..., Xn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn])
*
* Note: There is a tension between named and positional parameters here, which
* is impossible to resolve completely. Say you have
*
* C[type T], D[type U]
*
* Then do you expand `C & D` to `[T] -> C[T] & D[T]` or not? Under the named
* type parameter interpretation, this would be wrong whereas under the traditional
* higher-kinded interpretation this would be required. The problem arises from
* allowing both interpretations. A possible remedy is to be somehow stricter
* in where we allow which interpretation.
*/
private def liftIfHK(tp1: Type, tp2: Type, op: (Type, Type) => Type, original: (Type, Type) => Type) = {
val tparams1 = tp1.typeParams
Expand Down
14 changes: 4 additions & 10 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -428,16 +428,10 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
case tp: TypeRef =>
tp
case tp @ RefinedType(tp1, name: TypeName, rinfo) =>
rinfo 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))
}
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
8 changes: 1 addition & 7 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -549,13 +549,7 @@ object Types {

def goThis(tp: ThisType) = {
val d = go(tp.underlying)
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
if (d.exists) d
else
// There is a special case to handle:
// trait Super { this: Sub => private class Inner {} println(this.Inner) }
Expand Down
34 changes: 13 additions & 21 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ object Parsers {
private def simpleTypeRest(t: Tree): Tree = in.token match {
case HASH => simpleTypeRest(typeProjection(t))
case LBRACKET => simpleTypeRest(atPos(startOffset(t)) {
AppliedTypeTree(t, typeArgs(namedOK = true, wildOK = true)) })
AppliedTypeTree(t, typeArgs(namedOK = false, wildOK = true)) })
case _ => t
}

Expand Down Expand Up @@ -1664,7 +1664,7 @@ object Parsers {
/* -------- PARAMETERS ------------------------------------------- */

/** ClsTypeParamClause::= `[' ClsTypeParam {`,' ClsTypeParam} `]'
* ClsTypeParam ::= {Annotation} [{Modifier} type] [`+' | `-']
* ClsTypeParam ::= {Annotation} [`+' | `-']
* id [HkTypeParamClause] TypeParamBounds
*
* DefTypeParamClause::= `[' DefTypeParam {`,' DefTypeParam} `]'
Expand All @@ -1680,25 +1680,17 @@ object Parsers {
def typeParam(): TypeDef = {
val isConcreteOwner = ownerKind == ParamOwner.Class || ownerKind == ParamOwner.Def
val start = in.offset
var mods = annotsAsMods()
if (ownerKind == ParamOwner.Class) {
mods = modifiers(start = mods)
mods =
atPos(start, in.offset) {
if (in.token == TYPE) {
val mod = atPos(in.skipToken()) { Mod.Type() }
(mods | Param | ParamAccessor).withAddedMod(mod)
} else {
if (mods.hasFlags) syntaxError(TypeParamsTypeExpected(mods, ident()))
mods | Param | PrivateLocal
}
}
}
else mods = atPos(start) (mods | Param)
if (ownerKind != ParamOwner.Def) {
if (isIdent(nme.raw.PLUS)) mods |= Covariant
else if (isIdent(nme.raw.MINUS)) mods |= Contravariant
if (mods is VarianceFlags) in.nextToken()
val mods = atPos(start) {
annotsAsMods() | {
if (ownerKind == ParamOwner.Class) Param | PrivateLocal
else Param
} | {
if (ownerKind != ParamOwner.Def)
if (isIdent(nme.raw.PLUS)) { in.nextToken(); Covariant }
else if (isIdent(nme.raw.MINUS)) { in.nextToken(); Contravariant }
else EmptyFlags
else EmptyFlags
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message TypeParamsTypeExpected is now unused and could be removed.

}
}
atPos(start, nameStart) {
val name =
Expand Down
54 changes: 5 additions & 49 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -753,11 +753,10 @@ class Namer { typer: Typer =>
/* Check parent type tree `parent` for the following well-formedness conditions:
* (1) It must be a class type with a stable prefix (@see checkClassTypeWithStablePrefix)
* (2) If may not derive from itself
* (3) Overriding type parameters must be correctly forwarded. (@see checkTypeParamOverride)
* (4) The class is not final
* (5) If the class is sealed, it is defined in the same compilation unit as the current class
* (3) The class is not final
* (4) If the class is sealed, it is defined in the same compilation unit as the current class
*/
def checkedParentType(parent: untpd.Tree, paramAccessors: List[Symbol]): Type = {
def checkedParentType(parent: untpd.Tree): Type = {
val ptype = parentType(parent)(ctx.superCallContext)
if (cls.isRefinementClass) ptype
else {
Expand All @@ -772,8 +771,6 @@ class Namer { typer: Typer =>
ctx.error(i"cyclic inheritance: $cls extends itself$addendum", parent.pos)
defn.ObjectType
}
else if (!paramAccessors.forall(checkTypeParamOverride(pt, _)))
defn.ObjectType
else {
val pclazz = pt.typeSymbol
if (pclazz.is(Final))
Expand All @@ -785,47 +782,7 @@ class Namer { typer: Typer =>
}
}

/* Check that every parameter with the same name as a visible named parameter in the parent
* class satisfies the following two conditions:
* (1) The overriding parameter is also named (i.e. not local/name mangled).
* (2) The overriding parameter is passed on directly to the parent parameter, or the
* parent parameter is not fully defined.
* @return true if conditions are satisfied, false otherwise.
*/
def checkTypeParamOverride(parent: Type, paramAccessor: Symbol): Boolean = {
var ok = true
val pname = paramAccessor.name

def illegal(how: String): Unit = {
ctx.error(em"Illegal override of public type parameter $pname in $parent$how", paramAccessor.pos)
ok = false
}

def checkAlias(tp: Type): Unit = tp match {
case tp: RefinedType =>
if (tp.refinedName == pname)
tp.refinedInfo match {
case TypeAlias(alias) =>
alias match {
case TypeRef(pre, name1) if name1 == pname && (pre =:= cls.thisType) =>
// OK, parameter is passed on directly
case _ =>
illegal(em".\nParameter is both redeclared and instantiated with $alias.")
}
case _ => // OK, argument is not fully defined
}
else checkAlias(tp.parent)
case _ =>
}
if (parent.nonPrivateMember(paramAccessor.name).symbol.is(Param))
if (paramAccessor is Private)
illegal("\nwith private parameter. Parameter definition needs to be prefixed with `type'.")
else
checkAlias(parent)
ok
}

addAnnotations(denot.symbol, original)
addAnnotations(denot.symbol, original)

val selfInfo =
if (self.isEmpty) NoType
Expand Down Expand Up @@ -853,8 +810,7 @@ class Namer { typer: Typer =>

indexAndAnnotate(rest)(inClassContext(selfInfo))

val tparamAccessors = decls.filter(_ is TypeParamAccessor).toList
val parentTypes = ensureFirstIsClass(parents.map(checkedParentType(_, tparamAccessors)))
val parentTypes = ensureFirstIsClass(parents.map(checkedParentType(_)))
val parentRefs = ctx.normalizeToClassRefs(parentTypes, cls, decls)
typr.println(s"completing $denot, parents = $parents, parentTypes = $parentTypes, parentRefs = $parentRefs")

Expand Down
5 changes: 0 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,6 @@ object RefChecks {
isDefaultGetter(member.name) || // default getters are not checked for compatibility
memberTp.overrides(otherTp)

def domain(sym: Symbol): Set[Name] = sym.info.namedTypeParams.map(_.name)

//Console.println(infoString(member) + " overrides " + infoString(other) + " in " + clazz);//DEBUG

// return if we already checked this combination elsewhere
Expand Down Expand Up @@ -344,9 +342,6 @@ object RefChecks {
overrideError("cannot be used here - only term macros can override term macros")
} else if (!compatibleTypes) {
overrideError("has incompatible type" + err.whyNoMatchStr(memberTp, otherTp))
} else if (member.isType && domain(member) != domain(other)) {
overrideError("has different named type parameters: "+
i"[${domain(member).toList}%, %] instead of [${domain(other).toList}%, %]")
} else {
checkOverrideDeprecated()
}
Expand Down
22 changes: 3 additions & 19 deletions compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,7 @@ trait TypeAssigner {
}
}
else if (d.symbol is TypeParamAccessor)
if (d.info.isAlias)
ensureAccessible(d.info.bounds.hi, superAccess, pos)
else // It's a named parameter, use the non-symbolic representation to pick up inherited versions as well
d.symbol.owner.thisType.select(d.symbol.name)
ensureAccessible(d.info.bounds.hi, superAccess, pos)
else
ctx.makePackageObjPrefixExplicit(tpe withDenot d)
case _ =>
Expand Down Expand Up @@ -452,23 +449,10 @@ trait TypeAssigner {
}

def assignType(tree: untpd.AppliedTypeTree, tycon: Tree, args: List[Tree])(implicit ctx: Context) = {
assert(!hasNamedArg(args))
val tparams = tycon.tpe.typeParams
lazy val ntparams = tycon.tpe.namedTypeParams
def refineNamed(tycon: Type, arg: Tree) = arg match {
case ast.Trees.NamedArg(name, argtpt) =>
// Dotty deviation: importing ast.Trees._ and matching on NamedArg gives a cyclic ref error
val tparam = tparams.find(_.paramName == name) match {
case Some(tparam) => tparam
case none => ntparams.find(_.name == name).getOrElse(NoSymbol)
}
if (tparam.isTypeParam) RefinedType(tycon, name, argtpt.tpe.toBounds(tparam))
else errorType(i"$tycon does not have a parameter or abstract type member named $name", arg.pos)
case _ =>
errorType(s"named and positional type arguments may not be mixed", arg.pos)
}
val ownType =
if (hasNamedArg(args)) (tycon.tpe /: args)(refineNamed)
else if (sameLength(tparams, args)) tycon.tpe.appliedTo(args.tpes)
if (sameLength(tparams, args)) tycon.tpe.appliedTo(args.tpes)
else wrongNumberOfTypeArgs(tycon.tpe, tparams, args, tree.pos)
tree.withType(ownType)
}
Expand Down
Loading