Skip to content
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

Deprecate top-level wildcard type parameters #9712

Merged
merged 1 commit into from Oct 7, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 22 additions & 7 deletions src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Expand Up @@ -274,6 +274,14 @@ self =>
final val InBlock: Location = 1
final val InTemplate: Location = 2

type ParamOwner = Int
object ParamOwner {
final val Class = 0
final val Type = 1
final val TypeParam = 2 // unused
final val Def = 3
}

// These symbols may not yet be loaded (e.g. in the ide) so don't go
// through definitions to obtain the names.
lazy val ScalaValueClassNames = Seq(tpnme.AnyVal,
Expand Down Expand Up @@ -2554,8 +2562,9 @@ self =>
* TypeParam ::= Id TypeParamClauseOpt TypeBounds {`<%` Type} {`:` Type}
* }}}
*/
def typeParamClauseOpt(owner: Name, contextBoundBuf: ListBuffer[Tree]): List[TypeDef] = {
def typeParamClauseOpt(owner: Name, contextBoundBuf: ListBuffer[Tree], ownerKind: ParamOwner): List[TypeDef] = {
def typeParam(ms: Modifiers): TypeDef = {
val isAbstractOwner = ownerKind == ParamOwner.Type //|| ownerKind == ParamOwner.TypeParam
var mods = ms | Flags.PARAM
val start = in.offset
if (owner.isTypeName && isIdent) {
Expand All @@ -2570,10 +2579,16 @@ self =>
val nameOffset = in.offset
checkQMarkDefinition()
checkKeywordDefinition()
// TODO AM: freshTermName(o2p(in.skipToken()), "_$$"), will need to update test suite
val pname: TypeName = wildcardOrIdent().toTypeName
val pname: TypeName =
if (in.token == USCORE && (isAbstractOwner || !currentRun.isScala3)) {
if (!isAbstractOwner)
deprecationWarning(in.offset, "Top-level wildcard is not allowed and will error under -Xsource:3", "2.13.7")
in.nextToken()
freshTypeName("_$$")
}
else ident(skipIt = false).toTypeName
val param = atPos(start, nameOffset) {
val tparams = typeParamClauseOpt(pname, null) // @M TODO null --> no higher-order context bounds for now
val tparams = typeParamClauseOpt(pname, null, ParamOwner.Type) // @M TODO null --> no higher-order context bounds for now
TypeDef(mods, pname, tparams, typeBounds())
}
if (contextBoundBuf ne null) {
Expand Down Expand Up @@ -2903,7 +2918,7 @@ self =>
// [T : B] or [T : => B]; it contains the equivalent implicit parameter type,
// i.e. (B[T] or T => B)
val contextBoundBuf = new ListBuffer[Tree]
val tparams = typeParamClauseOpt(name, contextBoundBuf)
val tparams = typeParamClauseOpt(name, contextBoundBuf, ParamOwner.Def)
val vparamss = paramClauses(name, contextBoundBuf.toList, ofCaseClass = false)
newLineOptWhenFollowedBy(LBRACE)
var restype = fromWithinReturnType(typedOpt())
Expand Down Expand Up @@ -3005,7 +3020,7 @@ self =>
checkKeywordDefinition()
val name = identForType()
// @M! a type alias as well as an abstract type may declare type parameters
val tparams = typeParamClauseOpt(name, null)
val tparams = typeParamClauseOpt(name, null, ParamOwner.Type)
in.token match {
case EQUALS =>
in.nextToken()
Expand Down Expand Up @@ -3070,7 +3085,7 @@ self =>
atPos(start, if (name == tpnme.ERROR) start else nameOffset) {
savingClassContextBounds {
val contextBoundBuf = new ListBuffer[Tree]
val tparams = typeParamClauseOpt(name, contextBoundBuf)
val tparams = typeParamClauseOpt(name, contextBoundBuf, ParamOwner.Class)
classContextBounds = contextBoundBuf.toList
val tstart = (in.offset :: classContextBounds.map(_.pos.start)).min
if (!classContextBounds.isEmpty && mods.isTrait) {
Expand Down
21 changes: 11 additions & 10 deletions src/compiler/scala/tools/nsc/typechecker/Implicits.scala
Expand Up @@ -20,14 +20,13 @@ package tools.nsc
package typechecker

import scala.annotation.{nowarn, tailrec}
import scala.collection.mutable
import mutable.{LinkedHashMap, ListBuffer}
import scala.util.matching.Regex
import symtab.Flags._
import scala.collection.mutable, mutable.{LinkedHashMap, ListBuffer}
import scala.language.implicitConversions
import scala.reflect.internal.util.{ReusableInstance, Statistics, TriState}
import scala.reflect.internal.TypesStats
import scala.language.implicitConversions
import scala.tools.nsc.Reporting.WarningCategory
import scala.util.matching.Regex
import symtab.Flags._

/** This trait provides methods to find various kinds of implicits.
*
Expand Down Expand Up @@ -1830,7 +1829,7 @@ trait Implicits extends splain.SplainData {

private def interpolate(text: String, vars: Map[String, String]) =
Intersobralator.replaceAllIn(text, (_: Regex.Match) match {
case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "")
case Regex.Groups(v) => Regex.quoteReplacement(vars.getOrElse(v, ""))
// #3915: need to quote replacement string since it may include $'s (such as the interpreter's $iw)
case x => throw new MatchError(x)
})
Expand Down Expand Up @@ -1859,7 +1858,7 @@ trait Implicits extends splain.SplainData {
formatDefSiteMessage(typeArgsAtSym(paramTp).map(_.toString))

def formatDefSiteMessage(typeArgs: List[String]): String =
interpolate(msg, Map(symTypeParamNames zip typeArgs: _*))
interpolate(msg, Map(symTypeParamNames.zip(typeArgs): _*))

def formatParameterMessage(fun: Tree): String = {
val paramNames = referencedTypeParams
Expand All @@ -1880,13 +1879,15 @@ trait Implicits extends splain.SplainData {
case PolyType(tps, tr@TypeRef(_, _, tprefs)) =>
if (tps.corresponds(tprefs)((p, r) => p == r.typeSymbol)) tr.typeConstructor.toString
else {
val freshTpars = tps.mapConserve { case p if p.name == tpnme.WILDCARD => p.cloneSymbol.setName(newTypeName("?T" + tps.indexOf(p))) case p => p }
val freshTpars = tps.mapConserve { p =>
if (p.unexpandedName == tpnme.WILDCARD) p.cloneSymbol.setName(newTypeName("?T" + tps.indexOf(p)))
else p
}
som-snytt marked this conversation as resolved.
Show resolved Hide resolved
freshTpars.map(_.name).mkString("[", ", ", "] -> ") + tr.instantiateTypeParams(tps, freshTpars.map(_.typeConstructor)).toString
}

case tp => tp.toString
}
interpolate(msg, Map(paramNames zip argTypes: _*))
interpolate(msg, Map(paramNames.zip(argTypes): _*))
}

def validate: Option[String] = {
Expand Down
2 changes: 1 addition & 1 deletion src/partest/scala/tools/partest/ScaladocModelTest.scala
Expand Up @@ -72,7 +72,7 @@ abstract class ScaladocModelTest extends DirectTest {

try {
// 1 - compile with scaladoc and get the model out
val universe = model.getOrElse({sys.error("Scaladoc Model Test ERROR: No universe generated!")})
val universe = model.getOrElse { sys.error("Scaladoc Model Test ERROR: No universe generated!") }
// 2 - check the model generated
testModel(universe.rootPackage)
println("Done.")
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/internal/StdNames.scala
Expand Up @@ -473,6 +473,7 @@ trait StdNames {
def unexpandedName(name: Name): Name =
name.lastIndexOf("$$") match {
case 0 | -1 => name
case 1 if name.charAt(0) == '_' => if (name.isTermName) nme.WILDCARD else tpnme.WILDCARD
case idx0 =>
// Sketchville - We've found $$ but if it's part of $$$ or $$$$
// or something we need to keep the bonus dollars, so e.g. foo$$$outer
Expand Down
2 changes: 1 addition & 1 deletion src/reflect/scala/reflect/internal/TypeDebugging.scala
Expand Up @@ -140,7 +140,7 @@ trait TypeDebugging {
def debugString(tp: Type) = debug(tp)
}
def paramString(tp: Type) = typeDebug.str params tp.params
def typeParamsString(tp: Type) = typeDebug.str brackets (tp.typeParams map (_.defString))
def typeParamsString(tp: Type) = typeDebug.str.brackets(tp.typeParams.map(_.defString))
def debugString(tp: Type) = typeDebug debugString tp
}

Expand Down
6 changes: 2 additions & 4 deletions src/reflect/scala/reflect/internal/Types.scala
Expand Up @@ -1559,18 +1559,16 @@ trait Types
/** Bounds notation used in Scala syntax.
* For example +This <: scala.collection.generic.Sorted[K,This].
*/
private[internal] def scalaNotation(typeString: Type => String): String = {
private[internal] def scalaNotation(typeString: Type => String): String =
(if (emptyLowerBound) "" else " >: " + typeString(lo)) +
(if (emptyUpperBound) "" else " <: " + typeString(hi))
}
/** Bounds notation used in https://adriaanm.github.com/files/higher.pdf.
* For example *(scala.collection.generic.Sorted[K,This]).
*/
private[internal] def starNotation(typeString: Type => String): String = {
private[internal] def starNotation(typeString: Type => String): String =
if (emptyLowerBound && emptyUpperBound) ""
else if (emptyLowerBound) s"(${typeString(hi)})"
else s"(${typeString(lo)}, ${typeString(hi)})"
}
override def kind = "TypeBoundsType"
override def mapOver(map: TypeMap): Type = {
val lo1 = map match {
Expand Down
Expand Up @@ -45,7 +45,7 @@ trait ModelFactoryTypeSupport {
appendType0(tp)
case tp :: tps =>
appendType0(tp)
nameBuffer append sep
nameBuffer.append(sep)
appendTypes0(tps, sep)
}

Expand Down Expand Up @@ -202,15 +202,16 @@ trait ModelFactoryTypeSupport {
/* Polymorphic types */
case PolyType(tparams, result) =>
assert(tparams.nonEmpty, "polymorphic type must have at least one type parameter")
def typeParamsToString(tps: List[Symbol]): String = if (tps.isEmpty) "" else
tps.map{tparam =>
tparam.varianceString + tparam.name + typeParamsToString(tparam.typeParams)
}.mkString("[", ", ", "]")
nameBuffer append typeParamsToString(tparams)
def typeParamsToString(tps: List[Symbol]): String =
if (tps.isEmpty) ""
else
tps.map { tparam =>
tparam.varianceString + tparam.unexpandedName + typeParamsToString(tparam.typeParams)
}.mkString("[", ", ", "]")
nameBuffer.append(typeParamsToString(tparams))
appendType0(result)

case et@ExistentialType(quantified, underlying) =>

def appendInfoStringReduced(sym: Symbol, tp: Type): Unit = {
if (sym.isType && !sym.isAliasType && !sym.isClass) {
tp match {
Expand Down
2 changes: 1 addition & 1 deletion test/files/neg/t2462c.scala
@@ -1,4 +1,4 @@
// scalac: -Xfatal-warnings
// scalac: -Werror
//

import annotation._
Expand Down
16 changes: 16 additions & 0 deletions test/files/neg/t5606.check
@@ -0,0 +1,16 @@
t5606.scala:3: error: identifier expected but '_' found.
case class CaseTest[_](someData: String)
^
t5606.scala:5: error: using `?` as a type name requires backticks.
case class CaseTest_?[?](someData: String)
^
t5606.scala:8: error: identifier expected but '_' found.
case class CaseTest2[_, _](someData: String)
^
t5606.scala:11: error: identifier expected but '_' found.
def f[_](x: Int) = ???
^
t5606.scala:23: error: using `?` as a type name requires backticks.
def regress_?[F[?]] = 2
^
5 errors
26 changes: 26 additions & 0 deletions test/files/neg/t5606.scala
@@ -0,0 +1,26 @@
// scalac: -Xsource:3
// was: _ taken as ident of type param, but poor interactions below
case class CaseTest[_](someData: String)

case class CaseTest_?[?](someData: String)

// was: _ already defined
case class CaseTest2[_, _](someData: String)

class C {
def f[_](x: Int) = ???
}

object Test extends App {
def f0 = new CaseTest("X")
def f1: CaseTest[Int] = new CaseTest[Int]("X") // OK!
def f2: CaseTest[Int] = CaseTest[Int]("X") // CaseTest[Any]
def f3 = new CaseTest[Int]("X").copy() // CaseTest[Any]
def f4 = new CaseTest[Int]("X").copy[Int]() // CaseTest[Any]

def regress0[F[_]] = 0
def regress1[F[_, _]] = 1
def regress_?[F[?]] = 2
//def regress0[F[_$$1]] = 0;
//def regress1[F[_$$2, _$$3]] = 1
}
15 changes: 15 additions & 0 deletions test/files/neg/t5606b.check
@@ -0,0 +1,15 @@
t5606b.scala:4: warning: Top-level wildcard is not allowed and will error under -Xsource:3
case class CaseTest[_](someData: String)
^
t5606b.scala:7: warning: Top-level wildcard is not allowed and will error under -Xsource:3
case class CaseTest2[_, _](someData: String)
^
t5606b.scala:7: warning: Top-level wildcard is not allowed and will error under -Xsource:3
case class CaseTest2[_, _](someData: String)
^
t5606b.scala:10: warning: Top-level wildcard is not allowed and will error under -Xsource:3
def f[_](x: Int) = ???
^
error: No warnings can be incurred under -Werror.
4 warnings
1 error
11 changes: 11 additions & 0 deletions test/files/neg/t5606b.scala
@@ -0,0 +1,11 @@
// scalac: -Xlint -Werror
//
// was: _ taken as ident of type param, now a fresh name
case class CaseTest[_](someData: String)

// was: _ already defined, now a fresh name
case class CaseTest2[_, _](someData: String)

class C {
def f[_](x: Int) = ???
}
8 changes: 1 addition & 7 deletions test/files/neg/trailing-commas.check
Expand Up @@ -61,15 +61,9 @@ trait TypeArgs { def f: C[Int, String, ] }
trailing-commas.scala:23: error: identifier expected but ']' found.
trait TypeParamClause { type C[A, B, ] }
^
trailing-commas.scala:23: error: ']' expected but '}' found.
trait TypeParamClause { type C[A, B, ] }
^
trailing-commas.scala:24: error: identifier expected but ']' found.
trait FunTypeParamClause { def f[A, B, ] }
^
trailing-commas.scala:24: error: ']' expected but '}' found.
trait FunTypeParamClause { def f[A, B, ] }
^
trailing-commas.scala:26: error: identifier expected but ')' found.
trait SimpleType { def f: (Int, String, ) }
^
Expand Down Expand Up @@ -127,4 +121,4 @@ trait SimpleType2 { def f: (Int, ) }
trailing-commas.scala:48: error: ')' expected but '}' found.
trait SimpleType2 { def f: (Int, ) }
^
43 errors
41 errors
14 changes: 7 additions & 7 deletions test/files/pos/t5606.scala
@@ -1,9 +1,9 @@
// was: _ taken as ident of type param, now a fresh name
case class CaseTest[_](someData: String)

// was: _ already defined, now a fresh name
case class CaseTest2[_, _](someData: String)







case class CaseTest[_](someData:String)
class C {
def f[_](x: Int) = ???
}