Navigation Menu

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

Expose logic in refchecks to find unimplemented members of a class in reflection API. #8548

Open
scabug opened this issue Apr 30, 2014 · 6 comments

Comments

@scabug
Copy link

scabug commented Apr 30, 2014

Let's say one wished to find out via reflection (or with direct compiler internals access - the problem isn't limited to reflection) which of an abstract class's methods require implementations. This seems unambitious and realistic. How is one expected to go about it?

abstract class A           { def f: Int = 1 }
abstract class B extends A { def f: Int     }
abstract class C extends B

/**

// This returns the concrete f owned by A
scala> typeOf[C] member TermName("f")
res0: $r.intp.global.Symbol = method f 

// This returns nothing
scala> typeOf[C] decl TermName("f")
res1: $r.intp.global.Symbol = <none>

// And therefore inspecting all members and all decls of C we
// are never shown a deferred member.

scala> new C { }
<console>:37: error: object creation impossible, since there is a deferred declaration of method f in class B of type => Int which is not implemented in a subclass
              new C { }
                  ^

**/
@scabug
Copy link
Author

scabug commented Apr 30, 2014

Imported From: https://issues.scala-lang.org/browse/SI-8548?orig=1
Reporter: @paulp
Affected Versions: 2.11.0

@scabug
Copy link
Author

scabug commented Apr 30, 2014

@paulp said:
The only way to obtain the deferred member is to call decls on all the base types.

typeOf[C].baseTypeSeq.toList flatMap (_ decl TermName("f") alternatives) filter (_.isDeferred)
res22: List[$r.intp.global.Symbol] = List(method f)

And then you're still not done, because that net will scoop up every deferred member, even ones for which a qualifying concrete implementation exists. So you have to reimplement all the logic for what overrides what when, and maybe there's someone on earth who has a chance at doing that correctly but it's a lonely club. For example, if the deferred f's owner is a trait, then a concrete implementation is okay even if it's defined in one of f's direct parents. But if the owner is an abstract class, then another implementation is required.

@scabug
Copy link
Author

scabug commented Feb 9, 2015

@retronym said:
As you have shown, you can't answer this question by interrogating member or overrideChain'; they both respect the "concrete always overrides deferred" rule.

It seems you are asking for RefChecksTransformer#checkAllOverrides to be refactored to expose this logic.

Here's the copy/paste version:

abstract class A           { def f: Int = 1 }
abstract class B extends A { def f: Int     }
abstract class C extends B

object Test {
  val symtab = reflect.runtime.universe.asInstanceOf[reflect.internal.SymbolTable]
  import symtab._, reflect.internal.Flags._

  def ignoreDeferred(clazz: Symbol, member: Symbol) = (
    (member.isType && member.asType.isAbstractType && !member.isFBounded) || (
         member.isJavaDefined
      && javaErasedOverridingSym(clazz, member) != NoSymbol
    )
  )

  def javaErasedOverridingSym(clazz: Symbol, sym: Symbol): Symbol =
    clazz.tpe.nonPrivateMemberAdmitting(sym.name, BRIDGE).filter(other =>
      !other.isDeferred && other.isJavaDefined && !sym.enclClass.isSubClass(other.enclClass) && {
        def uncurryAndErase(tp: Type): Type = {
          erasure.erasure(sym)(uncurry.transformInfo(sym, tp))
        }
        val tp1: Type = uncurryAndErase(clazz.thisType.memberType(sym))
        val tp2: Type = uncurryAndErase(clazz.thisType.memberType(other))
        enteringPhase(findPhaseWithName("erasure"))(tp1 matches tp2)
      })

  def abstractDecls(clazz: Symbol): List[Symbol] = {
    val result = collection.mutable.Buffer[Symbol]()
    def loop(bc: Symbol): Unit = {
      for (decl <- bc.info.decls) {
        if (decl.isDeferred && !ignoreDeferred(clazz, decl)) {
          val impl = decl.matchingSymbol(clazz.thisType, admit = VBRIDGE)
          if (impl == NoSymbol || (decl.owner isSubClass impl.owner)) {
            result += decl
          }
        }
      }
      if (bc.superClass hasFlag ABSTRACT)
        loop(bc.superClass)
    }
    loop(clazz)
    result.toList
  }

  def main(args: Array[String]): Unit = {
    println(abstractDecls(symbolOf[C]))
  }
}
scalac-hash v2.11.5 sandbox/test.scala && scala-hash v2.11.5 -nobootcp -nc Test
warning: there were three feature warnings; re-run with -feature for details
one warning found
List(method f)

@scabug
Copy link
Author

scabug commented Feb 9, 2015

@paulp said:
I would less say I am asking for that logic to be exposed (does it really look like logic which should be "blessed", but anyway) as I am asking whether "isDeferred" means anything at all, and if it does, what it is that it means. When people call "isDeferred" what is it that they might reasonably expect to discover? What is it that they do? How can obtaining the set of unimplemented members in a class be demoted to a fringe pursuit? What is reflection for?

@scabug
Copy link
Author

scabug commented Feb 9, 2015

@retronym said:
AFAIK, isDeferred just means the method has no body. What that means in the context of some subclass of that methods owner is a something of a separate question, albeit a fair one to ask.

@scabug
Copy link
Author

scabug commented Feb 9, 2015

@paulp said:
It is a separate question given that the compiler implementation detail was exported under the guise of "isDeferred", but it is the relevant question in the user-facing reflection API. Given a class, there are members which have implementations by scala's rules and there are members which don't, and one should be able to distinguish them. Even if one thinks both the implementation detail and the abstract notion must be exposed, which is doubtful, if you were forced to pick one it couldn't be the implementation detail.

To call this a "new feature" is to admit that reflection is unusable.

@scabug scabug added this to the Backlog milestone Apr 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant