Skip to content

Commit d65ec2a

Browse files
authored
fix(codemodel-foundation): IntersectionTypeUsage.dependencies() now includes member types (#27)
1 parent 0027f19 commit d65ec2a

3 files changed

Lines changed: 109 additions & 14 deletions

File tree

codemodel-foundation/src/main/java/build/codemodel/foundation/usage/IntersectionTypeUsage.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,10 @@ public Stream<TypeUsage> types() {
120120

121121
@Override
122122
public Stream<TypeUsage> dependencies() {
123-
return traits(Dependent.class)
124-
.flatMap(Dependent::dependencies);
123+
return Streams.concat(
124+
types(),
125+
traits(Dependent.class)
126+
.flatMap(Dependent::dependencies));
125127
}
126128

127129
@Override
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package build.codemodel.foundation.usage;
2+
3+
/*-
4+
* #%L
5+
* Code Model Foundation
6+
* %%
7+
* Copyright (C) 2026 Workday, Inc.
8+
* %%
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
* #L%
21+
*/
22+
23+
import build.codemodel.foundation.ConceptualCodeModel;
24+
import build.codemodel.foundation.naming.NonCachingNameProvider;
25+
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.api.Test;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
/**
31+
* Unit tests for {@link IntersectionTypeUsage}.
32+
*
33+
* @author reed.vonredwitz
34+
* @since Apr-2026
35+
*/
36+
class IntersectionTypeUsageTests {
37+
38+
private ConceptualCodeModel codeModel;
39+
40+
@BeforeEach
41+
void onBeforeEach() {
42+
this.codeModel = new ConceptualCodeModel(new NonCachingNameProvider());
43+
}
44+
45+
/**
46+
* Ensures that {@link IntersectionTypeUsage#dependencies()} includes its member types,
47+
* consistent with {@link UnionTypeUsage#dependencies()}.
48+
*/
49+
@Test
50+
void shouldIncludeMemberTypesInDependencies() {
51+
final var naming = this.codeModel.getNameProvider();
52+
53+
final var serializableName = naming.getTypeName(java.io.Serializable.class);
54+
final var comparableName = naming.getTypeName(Comparable.class);
55+
56+
final var serializable = SpecificTypeUsage.of(this.codeModel, serializableName);
57+
final var comparable = SpecificTypeUsage.of(this.codeModel, comparableName);
58+
59+
final var intersection = IntersectionTypeUsage.of(this.codeModel, serializable, comparable);
60+
61+
assertThat(intersection.dependencies())
62+
.as("IntersectionTypeUsage.dependencies() should include all member types")
63+
.containsExactly(serializable, comparable);
64+
}
65+
66+
/**
67+
* Ensures that {@link IntersectionTypeUsage#dependencies()} is consistent with
68+
* {@link UnionTypeUsage#dependencies()} for the same member types.
69+
*/
70+
@Test
71+
void shouldHaveSameDependencyBehaviorAsUnionTypeUsage() {
72+
final var naming = this.codeModel.getNameProvider();
73+
74+
final var fooName = naming.getTypeName(String.class);
75+
final var barName = naming.getTypeName(Integer.class);
76+
77+
final var foo = SpecificTypeUsage.of(this.codeModel, fooName);
78+
final var bar = SpecificTypeUsage.of(this.codeModel, barName);
79+
80+
final var intersection = IntersectionTypeUsage.of(this.codeModel, foo, bar);
81+
final var union = UnionTypeUsage.of(this.codeModel, foo, bar);
82+
83+
assertThat(intersection.dependencies().toList())
84+
.as("IntersectionTypeUsage and UnionTypeUsage should both include member types in dependencies()")
85+
.isEqualTo(union.dependencies().toList());
86+
}
87+
}

docs/CODEBASE_MAP.md

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ JdkInitializer.initialize(CodeModel)
356356
- `FieldType`/`MethodType`/`ConstructorType` are reflection-path only; source-path descriptors lack raw reflection handles
357357
- `java.lang.Object` superclass is **not** modelled as `ExtendsTypeDescriptor` unless explicitly extended
358358
- `ResolvedMethod` only resolves methods in types already present in the `CodeModel`; JDK stdlib methods (e.g. `String.valueOf`) are never resolved
359-
- Unbounded wildcard `?` in reflection: `WildcardType.getUpperBounds()` returns `[Object.class]` — the code explicitly suppresses this
359+
- Unbounded wildcard `?` in reflection: `WildcardType.getUpperBounds()` returns `[Object.class]` for an unbounded `?``JDKCodeModel.getTypeUsage()` suppresses this and correctly produces a `WildcardTypeUsage` with no upper bound; fully contained inside that method
360360

361361
**Dependencies:** `objectoriented-codemodel`, `codemodel-framework`, `base-foundation`, `base-marshalling`, `base-telemetry-foundation`, `jakarta.inject-api`, JDK modules `java.compiler` + `jdk.compiler`
362362
**Depended on by:** `dependency-injection`, `codemodel-framework-builder`, `jdk-annotation-processor`
@@ -593,22 +593,28 @@ sequenceDiagram
593593

594594
**Plugin extensibility:** Implement `Enricher<T, E>`, `TypeChecker<T>`, `Compiler<T>`, or `Completer<T>` and register via `ServiceLoader` (`META-INF/services`). Use `@AutoService` to auto-generate the registration.
595595

596-
**TypeUsage equality:** String-based on `toString()`. A `TypeName` constructed with a `ModuleName` is **not** equal to the same logical type without one.
596+
**TypeUsage equality:** String-based on `toString()`. The reflection path includes a module prefix in `TypeName` (e.g. `java.base/java.lang.String`); the source path does not. Don't mix both paths for the same types in one `CodeModel`.
597597

598598
---
599599

600600
## Gotchas
601601

602-
- **`JdkInitializer` is single-use.** Throws on second `initialize()` call.
603-
- **Javac diagnostics are silenced.** Unresolvable types become `UnknownTypeUsage` without any error.
604-
- **`Object` superclass is never modelled.** Types that implicitly extend `Object` have no `ExtendsTypeDescriptor` trait.
605-
- **`ResolvedMethod` only resolves within the current `CodeModel`.** JDK stdlib methods (e.g. `String.valueOf`) are never resolved to `MethodDescriptor`.
606-
- **`ProviderResolver` is opt-in.** `Provider<T>` injection fails silently without `context.addResolver(ProviderResolver::new)`.
607-
- **`@Singular` registration is JVM-global and cached forever.** Classification cannot be changed between tests; create new `CodeModel` instances but expect shared trait metadata.
608-
- **`@Provides` methods with parameters are silently ignored** by `ProvidesResolver`.
609-
- **Override suppression is unconditional.** Overriding a method in a subclass removes the parent's `@Inject`, even if the override has no `@Inject` itself.
610-
- **`IntersectionTypeUsage.dependencies()` does not include its own `types()`.** Asymmetric vs `UnionTypeUsage`. Potentially a bug.
611-
- **`base-parsing` migration (PR #24):** The internal Pratt-parser classes (`ExpressionParser`, `Tokenizer`, `NodeResolver`, `TokenParser`, and all related token-parser types) were deleted from `expression-codemodel`. If you see references to these classes in branches or external forks, they no longer exist.
602+
Non-obvious behaviours that are working as designed but will surprise you if you don't know them.
603+
604+
**`jdk-codemodel`**
605+
- `JdkInitializer` is single-use — call `initialize()` a second time and it throws.
606+
- Javac diagnostics are suppressed — unresolvable types silently become `UnknownTypeUsage`. This is intentional; the source path is designed to be permissive.
607+
- Types that implicitly extend `Object` have no `ExtendsTypeDescriptor` trait. `Object` is the implicit root and is not modelled as an explicit parent.
608+
- `ResolvedMethod` is only attached when the declaring type is already in the `CodeModel`. Calls into JDK stdlib (e.g. `String.valueOf`) produce no trait — expected, since stdlib types aren't reverse-engineered unless explicitly loaded.
609+
- `FieldType`/`MethodType`/`ConstructorType` traits only exist on reflection-path descriptors. `MethodBodyDescriptor`/`FieldInitializerDescriptor` only exist on source-path descriptors.
610+
611+
**`dependency-injection`**
612+
- `ProviderResolver` must be added explicitly: `context.addResolver(ProviderResolver::new)`. `Provider<T>` injection does nothing without it.
613+
- Overriding a method removes the parent's `@Inject` even if the override has no `@Inject` — this is JSR-330 spec behaviour.
614+
- `@Provides` methods with parameters are silently skipped by `ProvidesResolver` (factory pattern requires no-arg methods).
615+
616+
**`base-parsing` migration**
617+
- The internal Pratt-parser classes (`ExpressionParser`, `Tokenizer`, `NodeResolver`, `TokenParser`, etc.) were deleted from `expression-codemodel` in PR #24. Parsing is now in `build.base:base-parsing`. References to those classes in old branches are dead.
612618

613619
---
614620

0 commit comments

Comments
 (0)