JDK-8319196 : ExecutableElement.getReceiverType doesn't return receiver types for methods loaded from bytecode
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2023-10-31
  • Updated: 2023-11-21
  • Resolved: 2023-11-14
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 22
22 b24Fixed
Related Reports
CSR :  
Relates :  
Relates :  
Sub Tasks
JDK-8319995 :  
Description
javac does not initialize receiver types of methods read from class files, which results in ExecutableElement.getReceiverType incorrectly reporting that methods don't have a receiver type.

Repro:

=== P.java
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

@SupportedAnnotationTypes("*")
public class P extends AbstractProcessor {
  @Override
  public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
  }

  boolean first = true;

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    if (first) {
      for (Element e :
          processingEnv.getElementUtils().getTypeElement("Test").getEnclosedElements()) {
        if (e.getSimpleName().contentEquals("f")) {
          ExecutableElement f = (ExecutableElement) e;
          processingEnv
              .getMessager()
              .printMessage(Diagnostic.Kind.NOTE, f.getReceiverType().toString(), f);
        }
      }
      first = false;
    }
    return false;
  }
}
=== Test.java
class Test {
  void f(Test this) {}
}
===

The receiver type is available if 'Test' is compiled from source:

$ javac -processor P -implicit:none Test.java
Test.java:2: Note: Test
  void f(Test this) {}
       ^

The receiver type is incorrectly reported as 'none' if 'Test' is loaded from the classpath:

$ javac -processor P -implicit:none Test
Note: none
Comments
Changeset: 346dbd6d Author: Liam Miller-Cushon <cushon@openjdk.org> Date: 2023-11-14 17:38:09 +0000 URL: https://git.openjdk.org/jdk/commit/346dbd6d1c1ac24da374dcdf4f432c0adf68efeb
14-11-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/16482 Date: 2023-11-02 18:01:20 +0000
02-11-2023

I found some related history in https://bugs.openjdk.org/browse/JDK-8225488 > Conceptually, a receiver type exists whether or not the syntax of a receiver parameter is present. However, javac's implementation of the language model only exposes non-trivial receiver types if the receiver parameter syntax was present in the sources. That bug was resolved by updating the implementation to return NOTYPE instead of null whenever a receiver parameter syntax is not explicitly present in source. It's entirely possible I'm missing something, but it still seems like getReceiverType() should return the implicit derived type for methods with receiver types, rather than NONE, regardless of of whether or not the type was present in source.
02-11-2023

Thinking about this a little more, it isn't possible to differentiate in bytecode whether a method had a receiver parameter explicitly declared in source, i.e. the following two programs current produce exactly the same output: class T { void f() {} } class T { void f(T this) {} } However I don't think this issue is specific to bytecode. The specification in https://docs.oracle.com/en/java/javase/21/docs/api/java.compiler/javax/lang/model/element/ExecutableElement.html#getReceiverType() says > An executable which is an instance method, or a constructor of an inner class, has a receiver type derived from the declaring type It doesn't say anything about whether there was an explicit `Foo this` parameter in source, I think getReceiverType() should return the type of the receiver instance (if there is one) regardless of whether it was explicit in source. i.e. it sounds like it is also a bug that the processor in the original report sees 'none' instead of 'Test' as the getReceiverType() of 'f': class Test { void f() {} }
01-11-2023