Skip to content

Fix #4373: reject wildcard types in syntactically invalid positions #4422

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 11 commits into from
May 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1130,9 +1130,7 @@ object desugar {
Apply(Select(Apply(Ident(nme.StringContext), strs), id), elems)
case InfixOp(l, op, r) =>
if (ctx.mode is Mode.Type)
if (!op.isBackquoted && op.name == tpnme.raw.AMP) AndTypeTree(l, r) // l & r
else if (!op.isBackquoted && op.name == tpnme.raw.BAR) OrTypeTree(l, r) // l | r
else AppliedTypeTree(op, l :: r :: Nil) // op[l, r]
AppliedTypeTree(op, l :: r :: Nil) // op[l, r]
else {
assert(ctx.mode is Mode.Pattern) // expressions are handled separately by `binop`
Apply(op, l :: r :: Nil) // op(l, r)
Expand Down
97 changes: 67 additions & 30 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ object Parsers {
if (isLeftAssoc(op1) != op2LeftAssoc)
syntaxError(MixedLeftAndRightAssociativeOps(op1, op2, op2LeftAssoc), offset)

def reduceStack(base: List[OpInfo], top: Tree, prec: Int, leftAssoc: Boolean, op2: Name): Tree = {
def reduceStack(base: List[OpInfo], top: Tree, prec: Int, leftAssoc: Boolean, op2: Name, isType: Boolean): Tree = {
if (opStack != base && precedence(opStack.head.operator.name) == prec)
checkAssoc(opStack.head.offset, opStack.head.operator.name, op2, leftAssoc)
def recur(top: Tree): Tree = {
Expand All @@ -464,7 +464,15 @@ object Parsers {
opStack = opStack.tail
recur {
atPos(opInfo.operator.pos union opInfo.operand.pos union top.pos) {
InfixOp(opInfo.operand, opInfo.operator, top)
val op = opInfo.operator
val l = opInfo.operand
val r = top
if (isType && !op.isBackquoted && op.name == tpnme.raw.BAR) {
OrTypeTree(checkAndOrArgument(l), checkAndOrArgument(r))
} else if (isType && !op.isBackquoted && op.name == tpnme.raw.AMP) {
AndTypeTree(checkAndOrArgument(l), checkAndOrArgument(r))
} else
InfixOp(l, op, r)
}
}
}
Expand All @@ -488,20 +496,20 @@ object Parsers {
var top = first
while (isIdent && isOperator) {
val op = if (isType) typeIdent() else termIdent()
top = reduceStack(base, top, precedence(op.name), isLeftAssoc(op.name), op.name)
top = reduceStack(base, top, precedence(op.name), isLeftAssoc(op.name), op.name, isType)
opStack = OpInfo(top, op, in.offset) :: opStack
newLineOptWhenFollowing(canStartOperand)
if (maybePostfix && !canStartOperand(in.token)) {
val topInfo = opStack.head
opStack = opStack.tail
val od = reduceStack(base, topInfo.operand, 0, true, in.name)
val od = reduceStack(base, topInfo.operand, 0, true, in.name, isType)
return atPos(startOffset(od), topInfo.offset) {
PostfixOp(od, topInfo.operator)
}
}
top = operand()
}
reduceStack(base, top, 0, true, in.name)
reduceStack(base, top, 0, true, in.name, isType)
}

/* -------- IDENTIFIERS AND LITERALS ------------------------------------------- */
Expand Down Expand Up @@ -709,15 +717,7 @@ object Parsers {
/** Same as [[typ]], but if this results in a wildcard it emits a syntax error and
* returns a tree for type `Any` instead.
*/
def toplevelTyp(): Tree = {
val t = typ()
findWildcardType(t) match {
case Some(wildcardPos) =>
syntaxError(UnboundWildcardType(), wildcardPos)
scalaAny
case None => t
}
}
def toplevelTyp(): Tree = checkWildcard(typ())

/** Type ::= [FunArgMods] FunArgTypes `=>' Type
* | HkTypeParamClause `->' Type
Expand Down Expand Up @@ -768,9 +768,16 @@ object Parsers {
accept(RPAREN)
if (imods.is(Implicit) || isValParamList || in.token == ARROW) functionRest(ts)
else {
for (t <- ts)
if (t.isInstanceOf[ByNameTypeTree])
syntaxError(ByNameParameterNotSupported())
val ts1 =
for (t <- ts) yield {
t match {
case t@ByNameTypeTree(t1) =>
syntaxError(ByNameParameterNotSupported(t), t.pos)
t1
case _ =>
t
}
}
val tuple = atPos(start) { makeTupleOrParens(ts) }
infixTypeRest(
refinedTypeRest(
Expand All @@ -784,7 +791,7 @@ object Parsers {
val start = in.offset
val tparams = typeParamClause(ParamOwner.TypeParam)
if (in.token == ARROW)
atPos(start, in.skipToken())(LambdaTypeTree(tparams, typ()))
atPos(start, in.skipToken())(LambdaTypeTree(tparams, toplevelTyp()))
else { accept(ARROW); typ() }
}
else infixType()
Expand Down Expand Up @@ -822,7 +829,7 @@ object Parsers {

def refinedTypeRest(t: Tree): Tree = {
newLineOptWhenFollowedBy(LBRACE)
if (in.token == LBRACE) refinedTypeRest(atPos(startOffset(t)) { RefinedTypeTree(t, refinement()) })
if (in.token == LBRACE) refinedTypeRest(atPos(startOffset(t)) { RefinedTypeTree(checkWildcard(t), refinement()) })
else t
}

Expand All @@ -835,7 +842,7 @@ object Parsers {
if (ctx.settings.strict.value)
deprecationWarning(DeprecatedWithOperator())
in.nextToken()
AndTypeTree(t, withType())
AndTypeTree(checkAndOrArgument(t), checkAndOrArgument(withType()))
}
else t

Expand Down Expand Up @@ -886,7 +893,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 = false, wildOK = true)) })
AppliedTypeTree(checkWildcard(t), typeArgs(namedOK = false, wildOK = true)) })
case _ => t
}

Expand Down Expand Up @@ -917,7 +924,7 @@ object Parsers {
else Nil
first :: rest
}
def typParser() = if (wildOK) typ() else toplevelTyp()
def typParser() = checkWildcard(typ(), wildOK)
if (namedOK && in.token == IDENTIFIER)
typParser() match {
case Ident(name) if in.token == EQUALS =>
Expand Down Expand Up @@ -1001,17 +1008,46 @@ object Parsers {
else if (location == Location.InPattern) refinedType()
else infixType()

/** Checks whether `t` is a wildcard type.
* If it is, returns the [[Position]] where the wildcard occurs.
/** Checks whether `t` represents a non-value type (wildcard types, or ByNameTypeTree).
* If it is, returns the [[Tree]] which immediately represents the non-value type.
*/
@tailrec
private final def findWildcardType(t: Tree): Option[Position] = t match {
case TypeBoundsTree(_, _) => Some(t.pos)
case Parens(t1) => findWildcardType(t1)
case Annotated(t1, _) => findWildcardType(t1)
private final def findNonValueTypeTree(t: Tree, alsoNonValue: Boolean): Option[Tree] = t match {
case TypeBoundsTree(_, _) => Some(t)
case ByNameTypeTree(_) if alsoNonValue => Some(t)
case Parens(t1) => findNonValueTypeTree(t1, alsoNonValue)
case Annotated(t1, _) => findNonValueTypeTree(t1, alsoNonValue)
case _ => None
}

def rejectWildcard(t: Tree, fallbackTree: Tree): Tree =
findNonValueTypeTree(t, false) match {
case Some(wildcardTree) =>
syntaxError(UnboundWildcardType(), wildcardTree.pos)
fallbackTree
case None => t
}


def checkWildcard(t: Tree, wildOK: Boolean = false, fallbackTree: Tree = scalaAny): Tree =
if (wildOK)
t
else
rejectWildcard(t, fallbackTree)

def checkAndOrArgument(t: Tree): Tree =
findNonValueTypeTree(t, true) match {
case Some(typTree) =>
typTree match {
case typTree: TypeBoundsTree =>
syntaxError(UnboundWildcardType(), typTree.pos)
case typTree: ByNameTypeTree =>
syntaxError(ByNameParameterNotSupported(typTree), typTree.pos)
}
scalaAny
case None => t
}

/* ----------- EXPRESSIONS ------------------------------------------------ */

/** EqualsExpr ::= `=' Expr
Expand Down Expand Up @@ -2148,7 +2184,7 @@ object Parsers {
in.token match {
case EQUALS =>
in.nextToken()
TypeDef(name, lambdaAbstract(tparams, typ())).withMods(mods).setComment(in.getDocComment(start))
TypeDef(name, lambdaAbstract(tparams, toplevelTyp())).withMods(mods).setComment(in.getDocComment(start))
case SUPERTYPE | SUBTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF =>
TypeDef(name, lambdaAbstract(tparams, typeBounds())).withMods(mods).setComment(in.getDocComment(start))
case _ =>
Expand Down Expand Up @@ -2276,7 +2312,8 @@ object Parsers {
/** ConstrApp ::= SimpleType {ParArgumentExprs}
*/
val constrApp = () => {
val t = annotType()
// Using Ident(nme.ERROR) to avoid causing cascade errors on non-user-written code
val t = checkWildcard(annotType(), fallbackTree = Ident(nme.ERROR))
if (in.token == LPAREN) parArgumentExprss(wrapNew(t))
else t
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,10 +696,10 @@ object messages {
}
}

case class ByNameParameterNotSupported()(implicit ctx: Context)
case class ByNameParameterNotSupported(tpe: untpd.TypTree)(implicit ctx: Context)
extends Message(ByNameParameterNotSupportedID) {
val kind = "Syntax"
val msg = "By-name parameter type not allowed here."
val msg = hl"By-name parameter type ${tpe} not allowed here."

val explanation =
hl"""|By-name parameters act like functions that are only evaluated when referenced,
Expand Down
51 changes: 51 additions & 0 deletions tests/neg/i4373.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
trait Base
trait TypeConstr[X]

class X1[A >: _ | X1[_]] // error
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add some examples involving annotations too? @_, _ @x, ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

class X2[A >: _ & X2[_]] // error
class X3[A >: X3[_] | _] // error
class X4[A >: X4[_] & _] // error
class X5[A >: _ with X5[_]] // error
class X6[A >: X6[_] with _] // error

class A1 extends _ // error
class A2 extends _ with _ // error // error
class A3 extends Base with _ // error
class A4 extends _ with Base // error

object Test {
type T1 = _ // error
type T2 = _[Int] // error
type T3 = _ { type S } // error
type T4 = [X] => _ // error

// Open questions:
type T5 = TypeConstr[_ { type S }] // error
type T6 = TypeConstr[_[Int]] // error

// expression types
type T7 = (=> Int) | (Int => Int) // error
type T8 = (=> Int) & (Int => Int) // error
type T9 = (=> Int) with (Int => Int) // error
type T10 = (Int => Int) | (=> Int) // error
type T11 = (Int => Int) & (=> Int) // error
type T12 = (Int => Int) with (=> Int) // error

// annotations
type T13 = _ @ annotation.tailrec // error
type T14 = Int @ _ // error
type T15 = (_ | Int) @ annotation.tailrec // error
type T16 = (Int | _) @ annotation.tailrec // error
type T17 = Int @ (_ | annotation.tailrec) // error
type T18 = Int @ (annotation.tailrec | _) // error

type T19 = (_ with Int) @ annotation.tailrec // error
type T20 = (Int with _) @ annotation.tailrec // error
type T21 = Int @ (_ with annotation.tailrec) // error // error
type T22 = Int @ (annotation.tailrec with _) // error // error

type T23 = (_ & Int) @ annotation.tailrec // error
type T24 = (Int & _) @ annotation.tailrec // error
type T25 = Int @ (_ & annotation.tailrec) // error
type T26 = Int @ (annotation.tailrec & _) // error
}
3 changes: 3 additions & 0 deletions tests/neg/i4373a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// ==> 040fb47fbaf718cecb11a7d51ac5a48bf4f6a1fe.scala <==
object x0 {
val x0 : _ with // error // error // error
4 changes: 4 additions & 0 deletions tests/neg/i4373b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// ==> 05bef7805687ba94da37177f7568e3ba7da1f91c.scala <==
class x0 {
x1: // error
x0 | _ // error // error
3 changes: 3 additions & 0 deletions tests/neg/i4373c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// ==> 18b253a4a89a84c5674165c6fc3efafad535eee3.scala <==
object x0 {
def x1[x2 <:_[ // error // error // error