Skip to content

Commit 413fd48

Browse files
Michael UsachenkoGitLab
authored andcommitted
Merge branch 'feat/847-java-permits-edges' into 'main'
feat(code-graph): extract Java sealed-type permits clauses into graph edges Closes #847 See merge request gitlab-org/orbit/knowledge-graph!1725
2 parents 0768c11 + a5666ce commit 413fd48

2 files changed

Lines changed: 118 additions & 6 deletions

File tree

crates/code-graph/src/v2/langs/generic/java.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,56 @@ mod tests {
393393
assert!(!meta.super_types.is_empty());
394394
}
395395

396+
#[test]
397+
fn permits_clause_names_reach_the_reference_stream() {
398+
// JEP 409 sealed types: the `permits` clause is not extracted into
399+
// metadata and produces no semantic Extends edge (#847) — in valid
400+
// Java the child-side extends/implements clause already provides it.
401+
// The permitted type names are still picked up by the bare
402+
// `reference("type_identifier")` rule, attributed to the sealed
403+
// parent, so the parent->child relationship stays reachable even
404+
// when a child file is missing or malformed.
405+
let probe = |code: &str| {
406+
JavaDsl::spec()
407+
.parse_full_collect(
408+
code.as_bytes(),
409+
"Test.java",
410+
crate::v2::config::Language::Java,
411+
&Tracer::new(false),
412+
)
413+
.unwrap()
414+
};
415+
416+
// Sealed interface
417+
let r = probe(
418+
"public sealed interface Shape permits Circle, Rectangle, Triangle {\n double area();\n}\n",
419+
);
420+
let shape_idx = r
421+
.definitions
422+
.iter()
423+
.position(|d| d.name == "Shape")
424+
.unwrap() as u32;
425+
for name in ["Circle", "Rectangle", "Triangle"] {
426+
let r#ref = r
427+
.refs
428+
.iter()
429+
.find(|x| x.name == name)
430+
.unwrap_or_else(|| panic!("{name} should appear as a reference"));
431+
assert_eq!(r#ref.enclosing_def, Some(shape_idx));
432+
}
433+
434+
// Sealed class, with extends/implements alongside permits
435+
let r = probe(
436+
"public sealed class Animal extends Creature implements Living permits Dog, Cat {\n}\n",
437+
);
438+
for name in ["Dog", "Cat"] {
439+
assert!(
440+
r.refs.iter().any(|x| x.name == name),
441+
"{name} should appear as a reference"
442+
);
443+
}
444+
}
445+
396446
#[test]
397447
fn imports_extracted() {
398448
let result =

crates/integration-tests-codegraph/fixtures/java/sealed_classes.yaml

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
# Java 17 sealed types (JEP 409): sealed interface with a permits clause and its
22
# permitted classes (final, non-sealed).
33
#
4-
# Known gap: the indexer does not extract the `permits` clause itself
5-
# (no handling of the tree-sitter `permits_clause` node in
6-
# crates/code-graph/src/v2/langs/generic/java.rs). The Extends edges asserted
7-
# below come from each permitted class's `implements Shape` clause, not from
8-
# `permits`. A sealed type whose permitted subtypes carry no resolvable
9-
# extends/implements clause would produce no permits-derived edges.
4+
# How `permits` is represented (#847): the clause is not extracted into
5+
# metadata and produces no semantic Extends edge — in valid Java every
6+
# permitted subtype carries its own extends/implements clause (JLS 8.1.1.2),
7+
# which is where the Extends edges asserted below come from. The permitted
8+
# type names in the clause (a `permits` field on class/interface_declaration
9+
# holding a `type_list`) are picked up by the bare `reference("type_identifier")`
10+
# rule and attributed to the sealed parent, so the parent->child relationship
11+
# stays reachable as a Calls edge even when the child file is missing or
12+
# malformed. The Quad/Square fixtures below pin that fallback.
1013
name: "Java 17: sealed interfaces and permitted classes"
1114
fixtures:
1215
- path: com/example/shapes/Shape.java
@@ -48,6 +51,27 @@ fixtures:
4851
public double area() { return 0; }
4952
}
5053
54+
# Malformed pair: Square does not declare `implements Quad` (illegal Java,
55+
# but indexable), so the parent-side `permits` clause is the only place the
56+
# relationship is stated.
57+
- path: com/example/shapes/Quad.java
58+
content: |
59+
package com.example.shapes;
60+
61+
public sealed interface Quad permits Square {
62+
double perimeter();
63+
}
64+
65+
- path: com/example/shapes/Square.java
66+
content: |
67+
package com.example.shapes;
68+
69+
public final class Square {
70+
private final double side;
71+
public Square(double side) { this.side = side; }
72+
public double perimeter() { return 4 * side; }
73+
}
74+
5175
tests:
5276
- name: "Sealed interface Shape is extracted as an Interface definition"
5377
query: |
@@ -86,3 +110,41 @@ tests:
86110
- { row: { child: "com.example.shapes.Circle", parent: "com.example.shapes.Shape", kind: Extends } }
87111
- { row: { child: "com.example.shapes.Rectangle", parent: "com.example.shapes.Shape", kind: Extends } }
88112
- { row: { child: "com.example.shapes.Triangle", parent: "com.example.shapes.Shape", kind: Extends } }
113+
114+
- name: "Permits-side references link the sealed parent to each permitted class"
115+
query: |
116+
MATCH (parent:Definition)-[e:DefinitionToDefinition]->(child:Definition)
117+
WHERE parent.fqn = 'com.example.shapes.Shape'
118+
AND e.edge_kind = 'Calls'
119+
AND child.fqn IN [
120+
'com.example.shapes.Circle',
121+
'com.example.shapes.Rectangle',
122+
'com.example.shapes.Triangle'
123+
]
124+
RETURN child.fqn AS child
125+
assert:
126+
- { row_count: 3 }
127+
- { row: { child: "com.example.shapes.Circle" } }
128+
- { row: { child: "com.example.shapes.Rectangle" } }
129+
- { row: { child: "com.example.shapes.Triangle" } }
130+
131+
- name: "Malformed child: no Extends edge exists between Square and Quad"
132+
query: |
133+
MATCH (child:Definition)-[e:DefinitionToDefinition]->(parent:Definition)
134+
WHERE child.fqn = 'com.example.shapes.Square'
135+
AND parent.fqn = 'com.example.shapes.Quad'
136+
AND e.edge_kind = 'Extends'
137+
RETURN child.fqn AS child
138+
assert:
139+
- { empty: true }
140+
141+
- name: "Malformed child: the permits-side reference is the only remaining link"
142+
query: |
143+
MATCH (parent:Definition)-[e:DefinitionToDefinition]->(child:Definition)
144+
WHERE parent.fqn = 'com.example.shapes.Quad'
145+
AND child.fqn = 'com.example.shapes.Square'
146+
AND e.edge_kind = 'Calls'
147+
RETURN child.fqn AS child
148+
assert:
149+
- { row_count: 1 }
150+
- { row: { child: "com.example.shapes.Square" } }

0 commit comments

Comments
 (0)