Skip to content

Commit ad055b4

Browse files
Apply external contracts to inherited receiver methods (#387)
An external specification for `JFrame` could not name inherited `pack()` or `setUndecorated(boolean)`. Both declarations produced warnings, and `pack(); setUndecorated(true);` incorrectly passed verification. Validate external method signatures and overload suggestions against methods the target actually inherits. Exclude private ancestor methods, interface static methods, and package-private methods lost across package boundaries. Resolve invocation contracts against the receiver's static type first, using exact parameter types to prevent equal-arity overload collisions, then retain the existing declaring-type lookup. This applies to both library calls and resolved source method calls. Add paired JFrame protocol, source inheritance, and JDK overload regressions, plus superclass fallback, subclass override, and inherited visibility coverage. Validation: `mvn -pl liquidjava-verifier test` — 394 tests, zero failures/errors. Targeted checks also verified the exact JFrame state error, both overload paths, and exactly three visibility warnings while accepting inherited protected and default-package methods. Adversarial and devil’s advocate review identified two implementation traps covered here: the existing typed context lookup falls back to arity, and Spoon’s complete method set includes non-inherited private/interface-static/package-private declarations. Receiver lookup uses a new exact API; inherited validation filters those declarations. Closes #379.
1 parent 9addae1 commit ad055b4

24 files changed

Lines changed: 282 additions & 3 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package testSuite.classes.inherited_external_correct;
2+
3+
import liquidjava.specification.ExternalRefinementsFor;
4+
import liquidjava.specification.StateRefinement;
5+
import liquidjava.specification.StateSet;
6+
7+
@StateSet({"displayable", "notDisplayable"})
8+
@ExternalRefinementsFor("javax.swing.JFrame")
9+
public interface JFrameRefinements {
10+
@StateRefinement(to = "notDisplayable(this)")
11+
void JFrame(String title);
12+
13+
@StateRefinement(to = "displayable(this)")
14+
void pack();
15+
16+
@StateRefinement(from = "notDisplayable(this)")
17+
void setUndecorated(boolean undecorated);
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testSuite.classes.inherited_external_correct;
2+
3+
import javax.swing.JFrame;
4+
5+
public class UseFrame {
6+
static void configure() {
7+
JFrame frame = new JFrame("example");
8+
frame.setUndecorated(true);
9+
frame.pack();
10+
}
11+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package testSuite.classes.inherited_external_error;
2+
3+
import liquidjava.specification.ExternalRefinementsFor;
4+
import liquidjava.specification.StateRefinement;
5+
import liquidjava.specification.StateSet;
6+
7+
@StateSet({"displayable", "notDisplayable"})
8+
@ExternalRefinementsFor("javax.swing.JFrame")
9+
public interface JFrameRefinements {
10+
@StateRefinement(to = "notDisplayable(this)")
11+
void JFrame(String title);
12+
13+
@StateRefinement(to = "displayable(this)")
14+
void pack();
15+
16+
@StateRefinement(from = "notDisplayable(this)")
17+
void setUndecorated(boolean undecorated);
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testSuite.classes.inherited_external_error;
2+
3+
import javax.swing.JFrame;
4+
5+
public class UseFrame {
6+
static void configure() {
7+
JFrame frame = new JFrame("example");
8+
frame.pack();
9+
frame.setUndecorated(true); // Expect: State Refinement Error
10+
}
11+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package testSuite.classes.inherited_external_visibility.parent;
2+
3+
public class Base {
4+
private void privateMethod() {}
5+
void packageMethod() {}
6+
protected void protectedMethod() {}
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package testSuite.classes.inherited_external_visibility;
2+
3+
public class Child extends testSuite.classes.inherited_external_visibility.parent.Base implements StaticParent {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testSuite.classes.inherited_external_visibility;
2+
3+
import liquidjava.specification.ExternalRefinementsFor;
4+
5+
@ExternalRefinementsFor("testSuite.classes.inherited_external_visibility.Child")
6+
public interface ChildRefinements {
7+
void privateMethod(); // Expect: Warning
8+
void packageMethod(); // Expect: Warning
9+
void interfaceStatic(); // Expect: Warning
10+
void protectedMethod();
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import liquidjava.specification.ExternalRefinementsFor;
2+
3+
class DefaultParent {
4+
void packageMethod() {}
5+
}
6+
7+
class DefaultChild extends DefaultParent {}
8+
9+
@ExternalRefinementsFor("DefaultChild")
10+
public interface DefaultChildRefinements {
11+
void packageMethod();
12+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package testSuite.classes.inherited_external_visibility;
2+
3+
public interface StaticParent {
4+
static void interfaceStatic() {}
5+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testSuite.classes.inherited_overload_correct;
2+
3+
import liquidjava.specification.*;
4+
5+
@ExternalRefinementsFor("java.util.Stack")
6+
@StateSet({"ready", "changed"})
7+
public interface StackRefinements<E> {
8+
@StateRefinement(to = "ready(this)") void Stack();
9+
@StateRefinement(to = "changed(this)") E remove(int index);
10+
@StateRefinement(from = "ready(this)") E peek();
11+
}

0 commit comments

Comments
 (0)