diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index e5021e5b955e..0ae11faf502b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index edaf6850d8cf..f3a1218f69aa 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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 = { @@ -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) } } } @@ -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 ------------------------------------------- */ @@ -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 @@ -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( @@ -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() @@ -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 } @@ -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 @@ -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 } @@ -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 => @@ -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 @@ -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 _ => @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 3abc06caddf7..ef685b0dc0f2 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -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, diff --git a/tests/neg/i4373.scala b/tests/neg/i4373.scala new file mode 100644 index 000000000000..6ba7c81a617a --- /dev/null +++ b/tests/neg/i4373.scala @@ -0,0 +1,51 @@ +trait Base +trait TypeConstr[X] + +class X1[A >: _ | X1[_]] // error +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 +} diff --git a/tests/neg/i4373a.scala b/tests/neg/i4373a.scala new file mode 100644 index 000000000000..d0e9505b8c44 --- /dev/null +++ b/tests/neg/i4373a.scala @@ -0,0 +1,3 @@ +// ==> 040fb47fbaf718cecb11a7d51ac5a48bf4f6a1fe.scala <== +object x0 { + val x0 : _ with // error // error // error diff --git a/tests/neg/i4373b.scala b/tests/neg/i4373b.scala new file mode 100644 index 000000000000..dee55114da66 --- /dev/null +++ b/tests/neg/i4373b.scala @@ -0,0 +1,4 @@ +// ==> 05bef7805687ba94da37177f7568e3ba7da1f91c.scala <== +class x0 { + x1: // error + x0 | _ // error // error diff --git a/tests/neg/i4373c.scala b/tests/neg/i4373c.scala new file mode 100644 index 000000000000..8f0246b6cdee --- /dev/null +++ b/tests/neg/i4373c.scala @@ -0,0 +1,3 @@ +// ==> 18b253a4a89a84c5674165c6fc3efafad535eee3.scala <== +object x0 { + def x1[x2 <:_[ // error // error // error