bug icon indicating copy to clipboard operation
bug copied to clipboard

Type error when outer scope name is shadowed by inheritance

Open counter2015 opened this issue 5 years ago • 5 comments

reproduction steps

Welcome to Scala 2.13.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_181).
Type in expressions for evaluation. Or try :help.

scala> def lazyMap[T, U](coll: Iterable[T], f: T => U) = 
     |   new Iterable[U] {
     |     def iterator = coll.iterator map f 
     |   }

problem

scala> def lazyMap[T, U](coll: Iterable[T], f: T => U) = 
     |   new Iterable[U] {
     |     def iterator = coll.iterator map f
     |   }
           def iterator = coll.iterator map f
                                            ^
On line 3: error: type mismatch;
        found   : T => U
        required: U => U

expectation

This code is from book Programming in Scala 3rd Edition. Section 24.14 Views

The type of coll should be Iterable[T] but complier judge it as Iterable[U] because the coll is as the same name the function coll defined in trait Iterable

In src/library/scala/collection/Iterable.scala

trait Iterable[+A] extends IterableOnce[A]
  with IterableOps[A, Iterable, Iterable[A]]
  with IterableFactoryDefaults[A, Iterable] {

  // The collection itself
  final def toIterable: this.type = this

  final protected def coll: this.type = this
....

I'm new to Scala, I'm not sure here is design as a feature or not.

One workaroud is rename the parameter

scala> def lazyMap[T, U](coll: Iterable[T], f: T => U) = {
     |   val _coll = coll
     |   new Iterable[U] {
     |     def iterator = _coll.iterator.map(f)
     |   }
     | }
lazyMap: [T, U](coll: Iterable[T], f: T => U)Iterable[U]

Consider the inner variable socpe will cover the outer ones, should add some hint here? Because a trait may have a lot of the function or variable, check variable inside defined trait one by one may not be so easy.

How about adding some hints like this?

scala> def lazyMap[T, U](coll: Iterable[T], f: T => U) = 
     |   new Iterable[U] {
     |     def iterator = coll.iterator map f
     |   }
           
           def lazyMap[T, U](coll: Iterable[T], f: T => U) = 
                                ^
On line 1: warning: variable defined in inner scope

     "coll" has defined in triat "Iterable", may be you need to rename it.

           def iterator = coll.iterator map f
                                            ^
On line 3: error: type mismatch;
        found   : T => U
        required: U => U

counter2015 avatar Mar 26 '20 13:03 counter2015

Wow, that's really unfortunate and unintuitive, IMHO! Thanks for the report.

About the warning suggestion, my gut feeling is that emitting a warning like that will cause a lot of false positives (i.e. cases where the external name is shadowed, but that isn't a problem).

But I wonder if this behaviour has a history. I've seen @odersky semi-recently argue in similar-ish situations (to do with import scopes and having code in 1 file or split into 2 files) something along the lines of "seen things should be seen". So, here, shouldn't the coll: Iterable[T] win over the inherited coll: Iterable[U]? 🤔

dwijnand avatar Mar 26 '20 14:03 dwijnand

That's indeed unfortunate. An immediate fix is to rename the coll in Iterable, which is right now defined like this:

  final protected def coll: this.type = this

It would be good to use a less common name for this.

About the problem in general: Maybe we should experiment with a warning or even an error. There is some precedence that a local import does not shadow an outer declaration. Example:

object a:
  def f = 1
object b:
  def f = 1
  object c:
    import a._
    f 

Here you get an ambiguity error.

7 |    f
  |    ^
  |    Reference to f is ambiguous
  |    it is both defined in object b
  |    and imported subsequently by import a._

The motivation for this is that we do not want a "less explicit" construct (the import) shadow a "more explicit" construct (the declaration). Analogously, we might not want to allow an inherited name to shadow an explicitly declared name in an outer scope.

odersky avatar Mar 26 '20 14:03 odersky

The PR here just takes all "foreign definitions" at precedence level 4, so that an inherited member defined elsewhere is like a package member defined elsewhere.

som-snytt avatar Mar 28 '20 22:03 som-snytt

The PR will be updated to align with dotty under -Xsource:3. JLS has an illustrative example at 6.5.7.1 (“Simple method names”).

som-snytt avatar Oct 08 '20 02:10 som-snytt

Opened a new PR because of the pushing to a closed PR means github won't allow reopening even though it makes no sense because computers.

som-snytt avatar Oct 04 '22 17:10 som-snytt