INRIA / INRIA/spoon

[Bug]: Exact import is ignored: simple name in `implements` binds to same-package class

Open
#6,867 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
2k
Forks
392
Avg merge
11h 24m
Merged PRs (30d)
36

Description

### Describe the bug

When a compilation unit has:
1. a **same-package type** with simple name `Foo`, and
2. a **single-type import** of a *different* `Foo` (`other.Foo`),
Spoon resolves the simple name `Foo` in `implements Foo` to the **same-package class**, not to the imported type.
This contradicts JLS 6.4.1: a single-type-import of `n` shadows a top-level type named `n` declared in another compilation unit of the same package.
We hit this in `noclasspath` mode (Spoon `11.5.1-beta-5`). The imported interface is often not in the model (external jar). Spoon still binds `implements Foo` to the same-package **class**, so `getTypeDeclaration()` is non-null and looks like a successful resolve.
Related: #465 (noclasspath `implements` / parameters not tied to imports). This case is sharper: a same-package class *steals* the binding, so callers cannot tell the resolve failed.

### Source code you are trying to analyze/transform

```Java
package demo;
public class Foo {
public void control(String a) {}
}

package demo;
import other.Foo;
public class FooImpl implements Foo {
public void control(String a) {}
public void extra() {}
}

package other;
public interface Foo {
void control(String a);
void extra();
}
```

### Source code for your Spoon processing

```Java
Launcher launcher = new Launcher();
launcher.getEnvironment().setNoClasspath(true);
launcher.getEnvironment().setShouldCompile(false);
launcher.addInputResource("src"); // both demo/ and other/ (or omit other/ to mimic missing jar)
launcher.buildModel();
CtType impl = launcher.getFactory().Type().get("demo.FooImpl");
CtTypeReference iface = impl.getSuperInterfaces().iterator().next();
System.out.println(iface.getQualifiedName());
System.out.println(iface.getTypeDeclaration() == null
? "null"
: iface.getTypeDeclaration().getQualifiedName()
+ " isInterface=" + iface.getTypeDeclaration().isInterface());
```

### Actual output

```Java
iface.getQualifiedName() is demo.Foo
getTypeDeclaration() returns the same-package class demo.Foo
```

### Expected output

```Java
iface.getQualifiedName() is other.Foo
declaration is the imported interface, or null if other.Foo is not in the model
it must not be demo.Foo
javac already treats implements Foo as other.Foo because of the import.
```

### Spoon Version

Spoon: 11.5.1-beta-5 (fr.inria.gforge.spoon:spoon-core)
Mode: setNoClasspath(true), empty sourceClasspath, setShouldCompile(false)
JDK used to run Spoon: 17+ (sources under analysis are Java 8)

### JVM Version

17

### What operating system are you using?

macOS

Contributor guide

Open the contributing guide

Research direction

Start by running the provided Launcher reproduction with noClasspath enabled and inspect the reference returned by getSuperInterfaces(). Check how getTypeDeclaration() resolves the simple name in this import and same-package scenario. Done means the reference resolves to other.Foo when available, or null when it is absent, and never to demo.Foo.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.