Skip to content

Disallow implicit conversions between unrelated types #2060

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
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ object CollectEntryPoints{
def fail(msg: String, pos: Position = sym.pos) = {
ctx.warning( sym.name +
s" has a main method with parameter type Array[String], but ${toDenot(sym).fullName} will not be a runnable program.\n Reason: $msg",
sourcePos(sym.pos)
pos
// TODO: make this next claim true, if possible
// by generating valid main methods as static in module classes
// not sure what the jvm allows here
Expand Down
18 changes: 9 additions & 9 deletions compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import scala.reflect.internal.util.WeakHashSet
import scala.reflect.io.{AbstractFile, Directory, PlainDirectory}
import scala.tools.asm.{AnnotationVisitor, ClassVisitor, FieldVisitor, MethodVisitor}
import scala.tools.nsc.backend.jvm.{BCodeHelpers, BackendInterface}
import dotty.tools.dotc.util.SourcePosition
import dotty.tools.dotc.core._
import Periods._
import SymDenotations._
Expand Down Expand Up @@ -481,13 +482,13 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma



implicit def positionHelper(a: Position): PositionHelper = new PositionHelper {
implicit class positionHelper(a: Position) extends PositionHelper {
def isDefined: Boolean = a.exists
def line: Int = sourcePos(a).line + 1
def line: Int = SourcePosition(a).line + 1
def finalPosition: Position = a
}

implicit def constantHelper(a: Constant): ConstantHelper = new ConstantHelper {
implicit class constantHelper(a: Constant) extends ConstantHelper {
def booleanValue: Boolean = a.booleanValue
def longValue: Long = a.longValue
def byteValue: Byte = a.byteValue
Expand All @@ -503,8 +504,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
def charValue: Char = a.charValue
}


implicit def treeHelper(a: Tree): TreeHelper = new TreeHelper {
implicit class treeHelper(a: Tree) extends TreeHelper {
def symbol: Symbol = a.symbol

def pos: Position = a.pos
Expand All @@ -517,7 +517,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
}


implicit def annotHelper(a: Annotation): AnnotationHelper = new AnnotationHelper {
implicit class annotHelper(a: Annotation) extends AnnotationHelper {
def atp: Type = a.tree.tpe

def assocs: List[(Name, Tree)] = assocsFromApply(a.tree)
Expand All @@ -538,7 +538,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
}


implicit def nameHelper(n: Name): NameHelper = new NameHelper {
implicit class nameHelper(n: Name) extends NameHelper {
def toTypeName: Name = n.toTypeName
def isTypeName: Boolean = n.isTypeName
def toTermName: Name = n.toTermName
Expand All @@ -551,7 +551,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
}


implicit def symHelper(sym: Symbol): SymbolHelper = new SymbolHelper {
implicit class symHelper(sym: Symbol) extends SymbolHelper {
// names
def fullName(sep: Char): String = sym.showFullName
def fullName: String = sym.showFullName
Expand Down Expand Up @@ -778,7 +778,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
}


implicit def typeHelper(tp: Type): TypeHelper = new TypeHelper {
implicit class typeHelper(tp: Type) extends TypeHelper {
def member(string: Name): Symbol = tp.member(string.toTermName).symbol

def isFinalType: Boolean = tp.typeSymbol is Flags.Final //in scalac checks for type parameters. Why? Aren't they gone by backend?
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ object desugar {
vparamss = (setterParam :: Nil) :: Nil,
tpt = TypeTree(defn.UnitType),
rhs = setterRhs
).withMods((mods | Accessor) &~ CaseAccessor) // rhs gets filled in later, when field is generated and getter has parameters
).withMods((mods | Accessor) &~ (CaseAccessor | Implicit)) // rhs gets filled in later, when field is generated and getter has parameters
Thicket(vdef, setter)
}
else vdef
Expand Down
12 changes: 0 additions & 12 deletions compiler/src/dotty/tools/dotc/core/Decorators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ package core
import annotation.tailrec
import Symbols._
import Contexts._, Names._, Phases._, printing.Texts._, printing.Printer, printing.Showable
import util.Positions.Position, util.SourcePosition
import collection.mutable.ListBuffer
import dotty.tools.dotc.transform.TreeTransforms._
import ast.tpd._
import scala.language.implicitConversions
import printing.Formatting._

Expand Down Expand Up @@ -149,16 +147,6 @@ object Decorators {
}
}

implicit def sourcePos(pos: Position)(implicit ctx: Context): SourcePosition = {
def recur(inlinedCalls: List[Tree], pos: Position): SourcePosition = inlinedCalls match {
case inlinedCall :: rest =>
sourceFile(inlinedCall).atPos(pos).withOuter(recur(rest, inlinedCall.pos))
case empty =>
ctx.source.atPos(pos)
}
recur(enclosingInlineds, pos)
}

implicit class StringInterpolators(val sc: StringContext) extends AnyVal {

/** General purpose string formatting */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class CollectEntryPoints extends MiniPhaseTransform {
def fail(msg: String, pos: Position = sym.pos) = {
ctx.warning(sym.name +
s" has a main method with parameter type Array[String], but ${sym.fullName} will not be a runnable program.\n Reason: $msg",
sourcePos(sym.pos)
pos
// TODO: make this next claim true, if possible
// by generating valid main methods as static in module classes
// not sure what the jvm allows here
Expand Down
16 changes: 14 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -501,10 +501,22 @@ trait Checking {
/** Check that a non-implicit parameter making up the first parameter section of an
* implicit conversion is not a singleton type.
*/
def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = vparamss match {
def checkImplicitConversion(sym: Symbol, vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = vparamss match {
case (vparam :: Nil) :: _ if !(vparam.symbol is Implicit) =>
if (vparam.tpt.tpe.isInstanceOf[SingletonType])
ctx.error(s"implicit conversion may not have a parameter of singleton type", vparam.tpt.pos)
if (!ctx.isAfterTyper && sym.owner.isClass && !sym.is(AccessFlags)) {
val fromTpe = vparam.tpt.tpe
val toTpe = sym.info.finalResultType
val fromClasses = fromTpe.classSymbols
val toClasses = toTpe.classSymbols
def isLocal(cls: ClassSymbol) =
ctx.owner.topLevelClass.associatedFile == cls.associatedFile
if (!fromClasses.exists(isLocal) && !toClasses.forall(isLocal))
ctx.errorOrMigrationWarning(
em"""implicit conversion must be defined in same compilation unit as its source ${fromClasses.mkString(" or ")}
|or its target ${toClasses.mkString(" and ")}""", sym.pos)
}
case _ =>
}

Expand Down Expand Up @@ -633,7 +645,7 @@ trait NoChecking extends Checking {
override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree
override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()
override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp
override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = ()
override def checkImplicitConversion(sym: Symbol, vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = ()
override def checkFeasible(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp
override def checkInlineConformant(tree: Tree, what: => String)(implicit ctx: Context) = ()
override def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = ()
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1219,7 +1219,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
completeAnnotations(ddef, sym)
val tparams1 = tparams mapconserve (typed(_).asInstanceOf[TypeDef])
val vparamss1 = vparamss nestedMapconserve (typed(_).asInstanceOf[ValDef])
if (sym is Implicit) checkImplicitParamsNotSingletons(vparamss1)
if (sym is Implicit) checkImplicitConversion(sym, vparamss1)
var tpt1 = checkSimpleKinded(typedType(tpt))

var rhsCtx = ctx
Expand Down
15 changes: 15 additions & 0 deletions compiler/src/dotty/tools/dotc/util/SourcePosition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ extends interfaces.SourcePosition {
else s"(no source file, offset = ${pos.point})"
}

object SourcePosition {
import core.Contexts.Context
import ast.tpd.{Tree, sourceFile, enclosingInlineds}

implicit def apply(pos: Position)(implicit ctx: Context): SourcePosition = {
def recur(inlinedCalls: List[Tree], pos: Position): SourcePosition = inlinedCalls match {
case inlinedCall :: rest =>
sourceFile(inlinedCall).atPos(pos).withOuter(recur(rest, inlinedCall.pos))
case empty =>
ctx.source.atPos(pos)
}
recur(enclosingInlineds, pos)
}
}

/** A sentinel for a non-existing source position */
@sharable object NoSourcePosition extends SourcePosition(NoSource, NoPosition) {
override def toString = "?"
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/io/Jar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import scala.language.{postfixOps, implicitConversions}
// static Attributes.Name SPECIFICATION_VERSION

class Jar(file: File) extends Iterable[JarEntry] {
import Jar.enrichManifest

def this(jfile: JFile) = this(File(jfile))
def this(path: String) = this(File(path))

Expand Down Expand Up @@ -133,6 +135,8 @@ object Jar {
def apply(manifest: JManifest): WManifest = new WManifest(manifest)
implicit def unenrichManifest(x: WManifest): JManifest = x.underlying
}
implicit def enrichManifest(m: JManifest): WManifest = WManifest(m)

class WManifest(manifest: JManifest) {
for ((k, v) <- initialMainAttrs)
this(k) = v
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/io/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ package object io {
type JManifest = java.util.jar.Manifest
type JFile = java.io.File

implicit def enrichManifest(m: JManifest): Jar.WManifest = Jar.WManifest(m)
private lazy val daemonThreadPool = DaemonThreadFactory.newPool()

def runnable(body: => Unit): Runnable = new Runnable { override def run() = body }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import core.Flags
object ModifiersParsingTest {
implicit val ctx: Context = (new ContextBase).initialCtx

implicit def parse(code: String): Tree = {
private implicit def parse(code: String): Tree = {
val (_, stats) = new Parser(new SourceFile("<meta>", code.toCharArray)).templateStatSeq()
stats match { case List(stat) => stat; case stats => Thicket(stats) }
}
Expand Down
2 changes: 1 addition & 1 deletion library/src/scala/reflect/Selectable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Selectable(val receiver: Any) extends AnyVal with scala.Selectable {
}

object Selectable {
implicit def reflectiveSelectable(receiver: Any): scala.Selectable = receiver match {
implicit def reflectiveSelectable(receiver: Any): Selectable = receiver match {
case receiver: scala.Selectable => receiver
case _ => new Selectable(receiver)
}
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/function-arity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ object Test {
foo((a: Int, b: String) => a + b) // error: none of the overloaded alternatives of method foo match arguments (Int, Int)
}
object jasonComment {
implicit def i2s(i: Int): String = i.toString
private implicit def i2s(i: Int): String = i.toString
((x: String, y: String) => 42) : (((Int, Int)) => String) // error
}
2 changes: 1 addition & 1 deletion tests/neg/implicitDefs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ object implicitDefs {

implicit val x = 2 // error: type of implicit definition needs to be given explicitly
implicit def y(x: Int) = 3 // error: result type of implicit definition needs to be given explicitly
implicit def z(a: x.type): String = "" // error: implicit conversion may not have a parameter of singleton type
implicit def z(a: x.type): String = "" // error: implicit conversion may not have a parameter of singleton type // error: implicit must be defined in same compilation unit

def foo(implicit x: String) = 1

Expand Down
2 changes: 1 addition & 1 deletion tests/neg/zoo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ def newLion: Lion = new {
}
val milka = newCow
val leo = newLion
leo.eats(milka) // error: no projector found
leo.eats(milka) // error found: Test.Lion(Test.leo) required: Selectable
}
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/pos/t0438.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Foo {
implicit def pair2fun2[A, B, C](f: (A, B) => C): ((A, B)) => C =
private implicit def pair2fun2[A, B, C](f: (A, B) => C): ((A, B)) => C =
{p: (A, B) => f(p._1, p._2) }

def foo(f: ((Int, Int)) => Int) = f
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/typealiases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ trait Test[T] {
object main extends Test[Int] {
val pair1 = (1,1)

implicit def topair(x: Int): Tuple2[Int, Int] = (x,x)
private implicit def topair(x: Int): Tuple2[Int, Int] = (x,x)
val pair2: MyPair[Int] = 1
val x: Short = 1
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions tests/run/implicits.check
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
OK
[2]
Str(OK)
Str([2])
11 changes: 6 additions & 5 deletions tests/run/implicits.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import scala.language.implicitConversions

case class Str(str: String)
object A {
object B {
implicit def int2string(x: Int): String = "["+x.toString+"]"
implicit def int2string(x: Int): Str = Str("["+x.toString+"]")
}
}

Expand All @@ -12,18 +13,18 @@ class C(x: String) {
}

object Inner {
val s: String = x
implicit def Inner2String(x: Inner): String = s
val s: Str = Str(x)
implicit def Inner2Str(x: Inner): Str = s
}
}

object Test extends dotty.runtime.LegacyApp {
import A.B._
val c = new C("OK")
val i = new c.Inner
val s: String = i
val s: Str = i
Console.println(s)
Console.println(2: String)
Console.println(2: Str)
}

object TestPriority {
Expand Down
8 changes: 4 additions & 4 deletions tests/run/tcpoly_parseridioms.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ trait ParserIdioms extends Parsers with Idioms {
def pure[a](x: a): Parser[a] = success(x)
}

implicit def parserIdiomFun[s, t](fun: s=>t): IdiomaticFunction[Parser, ParserIdiom.type, s, t] =
protected implicit def parserIdiomFun[s, t](fun: s=>t): IdiomaticFunction[Parser, ParserIdiom.type, s, t] =
new IdiomaticFunction[Parser, ParserIdiom.type, s, t](ParserIdiom, fun)
implicit def parserIdiomTgt[s](tgt: s): IdiomaticTarget[Parser, ParserIdiom.type, s] =
protected implicit def parserIdiomTgt[s](tgt: s): IdiomaticTarget[Parser, ParserIdiom.type, s] =
new IdiomaticTarget[Parser, ParserIdiom.type, s](ParserIdiom, tgt)

trait Expr
Expand All @@ -103,8 +103,8 @@ trait ParserIdioms extends Parsers with Idioms {
// TODO: how can parserIdiom(curry2(_)) be omitted?
def expr: Parser[Expr] = parserIdiomFun(curry2(Plus)) <| num <> num |>

implicit def curry2[s,t,u](fun: (s, t)=>u)(a: s)(b: t): u = fun(a, b)
implicit def curry3[r,s,t,u](fun: (r,s, t)=>u)(a: r)(b: s)(c: t): u = fun(a, b, c)
protected implicit def curry2[s,t,u](fun: (s, t)=>u)(a: s)(b: t): u = fun(a, b)
protected implicit def curry3[r,s,t,u](fun: (r,s, t)=>u)(a: r)(b: s)(c: t): u = fun(a, b, c)
}

object Test extends ParserIdioms with App {
Expand Down