-
Notifications
You must be signed in to change notification settings - Fork 317
Add generic type info to JavaClass
#398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My comments so far are only from reading the code itself.
I haven't really used it yet; in particular, I'd still like to
- measure the time to import a larger project, and
- of course play with new API in my own rules.
archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypesAssertion.java
Outdated
Show resolved
Hide resolved
archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java
Outdated
Show resolved
Hide resolved
archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java
Outdated
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeTest.java
Outdated
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java
Outdated
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java
Show resolved
Hide resolved
...it/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java
Outdated
Show resolved
Hide resolved
.../src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableOfClassAssertion.java
Outdated
Show resolved
Hide resolved
archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java
Outdated
Show resolved
Hide resolved
I assume that extending |
Yeah, I think this PR is already quite big and provides a benefit by itself, so I would add it do the |
@hankem thanks a lot for your intensive review 😃 I know that this is not the smallest / easiest PR to review 😂 But yes, if you want to play around a little and measure performance, I'd really appreciate this! I can wait for this a couple more days 😉 |
fffddcb
to
c5647cc
Compare
The increase in runtime can be detected, but seems acceptable. (The import has obviously more work to do.)
(based on only 5 repetitions of the import so far) |
Thanks for testing! 😃 I expected something in that area, won't come for free 😉 Guess I'll profile it to make sure there is no unnecessary waste in there though, since it is a 10% increase after all 😞 But if this is unavoidable it would also be possible to make generic support configurable very easily, if it should be a real problem for some people. |
Hmm, I found the place where the performance drain happens and it's actually not related to the additional generic support per se, but that the order of traversing the classes is now relevant for the import process 🤔 I'll look if this can be solved differently. Because without that I see almost no performance loss when I import the whole classpath of ArchUnit (~40000 classes) |
c5647cc
to
d51ed9b
Compare
@hankem I tried to remove the source of the performance loss, can you check again if that is reflected in your performance test as well? |
4657067
to
d0863d5
Compare
We need to free the name `JavaType` if we want to add support for generics to be consistent with the Reflection API. I.e. if the Reflection API provides a super type `Type` for `Class` and `TypeVariable`, we should probably mimic this as `JavaType`, `JavaClass` and `JavaTypeVariable`. Otherwise we break the consistency with the Reflection API there, making the API confusing. Note that I do not consider `JavaClassDescriptor` the best name in the universe, but merely could not come up with anything better. It is pretty much all the information we can parse out of the info parsed by ASM, name parts, component type, etc. Signed-off-by: Peter Gafert <[email protected]>
Similar to the Reflection API we will need a common super type of `JavaClass` and `TypeVariable` to describe generic bounds. Signed-off-by: Peter Gafert <[email protected]>
Instead of `JavaClass(es)Assertion` we will need `JavaType(s)Assertion` in the future to be able to handle generic test cases. We can easily allow assertions on `JavaType` by simply pre-asserting `instanceof JavaClass` for the relevant cases specific to `JavaClass`. We could introduce a special sub-assertion `JavaClassAssertion` extending `JavaTypeAssertion`, where an assertion of the correct sub type is not necessary anymore, but to me the complexity seems to be fairly low to simply assert the correct sub type where necessary, so I kept it simple and only introduced `JavaType(s)Assertion`. Signed-off-by: Peter Gafert <[email protected]>
d0863d5
to
f49af67
Compare
Impressive! With your performance-tuned
I was wondering whether |
Honestly I'm not sure if it's really that change, or the change to |
Introduce `JavaTypeVariable` to model type parameters like `MyClass<T>`. For now the model is quite simple, it only supports type bounds that are not generic themselves. In fact the current implementation will behave wrongly, as soon as there are types like `MyClass<T extends SomeGeneric<AnotherType>>`. However the current state already shows correct behavior for simple cases like `MyClass<T extends SomeClass & SomeInterface, U extends YetAnotherClass & More & Interfaces>`. Signed-off-by: Peter Gafert <[email protected]>
We now support importing the first level of generic bounds of type parameters where type parameters are assigned to concrete classes. E.g. `SomeClass<T extends Foo<String> & Bar<Integer>>`. For now we still do not support this in a generic way, i.e. `SomeClass<T extends Foo<Bar<Integer>>` is not supported correctly (bounds nested more than one level will be ignored, in this case `Integer`). Signed-off-by: Peter Gafert <[email protected]>
We now support wildcards at the first level of parameterized type bounds. E.g. wildcards in bounds like `T extends List<? extends Serializable>`. To support quick localizations of errors in assertions I have added a `DescriptionContext` which can trace the steps (i.e. upper bound of second parameter of third type parameter) through the assertion and print the necessary details if a recursive assertion fails. Signed-off-by: Peter Gafert <[email protected]>
We now support signatures like `Foo<S, T extends S>`, where one type parameter is bound by another type parameter. What makes this difficult is the fact that `extends S` in this case is a reference to the original type parameter `S` (same holds for the Java Reflection API). I.e. in a case like `Foo<S extends String, T extends S>` it must be possible to retrieve `typeParameters[1].upperBounds[0].upperBounds[0]` to be `java.lang.String`. So to build the type parameter `T`, we need the type parameter `S` already built so we can reference it. To support this, I have split the creation of type parameters into `build()`, which will skip the bounds (since we need the built type parameters for this), and `complete` where the bounds are finished and set into the type parameters (thus unfortunately removing immutability, but I do not think you can have this in such a chicken and egg scenario with Java capabilities; in any way the mutability is package private, from an API point of view this object will be immutable like all ArchUnit domain objects). Unfortunately this is a fairly recursive thing, so each builder in the type parameter bounds tree needs all constructed type parameters, because it might have bounds or assigned type parameters somewhere down the line that are type parameters themselves. Within the import process we now need one more level of indirection, because we must decide if this is a new type to build (like a parameterized type) or if it simply references another type parameter and thus needs to return an already constructed one. Therefore there is now `JavaTypeCreationProcess` to encapsulate these two cases (technically it would have been possible to hack `TypeVariableBuilder` to return an existing type variable on build, but I have decided against it, since it would be a violation of the builder pattern and thus surprising). Signed-off-by: Peter Gafert <[email protected]>
So far we support signatures like `Foo<S, T extends S>`, where one type parameter is bound by another type parameter. However it is possible, that type parameters of inner classes refer to type parameters of outer classes, e.g. `class Foo<T> { class Inner<U extends T> {} }`. This means that we not only need all constructed type parameters of the class under construction to finish the type arguments, but also all type parameters of all enclosing classes. Unfortunately enclosing class creation has been too late in the construction process for this, thus I have moved it to an earlier place, namely when type hierarchies are assembled. Also to ensure that the construction of the type parameters occurs in the correct order (after all we need the type parameters of the enclosing class constructed before we can process the type parameters of the inner class) I have added sorting by type name to the processing of imported classes. I realize that this is somewhat implicit to ensure the correct order this way (i.e. rely on the naming, that inner class names start with the outer classes name and thus sorting the class names lexicographically will result in the outer classes being processed first), but I still opted for this solution, because I think the complexity to integrate it into the current code base is low and the solution should not impact performance in a noticeable way. In the central place I added a comment and made the internal API clear by replacing the return type with `Sorted...` (at least to point out that the order is of relevance). Should this be broken in the future, tests will likely notice the problem, even though the symptom might unfortunately just be flaky tests. Signed-off-by: Peter Gafert <[email protected]>
We can now also import upper and lower bounds of wildcards that are type variables, i.e. within `Foo<T, U extends List<? extends T>, V extends List<? super T>` we now support detecting `? extends T` and `? super T`. Signed-off-by: Peter Gafert <[email protected]>
Reduced the number of visitor objects needed and generalize the design. We will need to apply more recursion to be able to follow the tree of arbitrarily nested types, e.g. `Foo<Bar<Baz<? extends Quux<T>, ?>, ?>, T>`. It will be possible to recursively reuse the type argument processors in the next step. Signed-off-by: Peter Gafert <[email protected]>
We now traverse the tree of type parameters recursively, thus supporting an arbitrarily nested tree of type parameters. I have thought about reusing the same visitor(s) to traverse the tree to reduce the number of created objects, but decided it is not worth the complexity at the moment. If we create new visitors for each level we have an unmodified clean state once we come back from a nested level without the need to clean up / implement `visitEnd()`. Also in general such nested type signatures are a real corner case, most type signatures have at max one level of nexted bounds, so together with only a small subset of used types even being generic I decided to go for simplicity and safety instead of premature optimization (before we would introduce extra complexity here, we should measure this on some real life code bases if this really provides any measurable performance boost). Signed-off-by: Peter Gafert <[email protected]>
So far types like `Foo<T extends List<SomeType[]>>` were not correctly handled. We now support any arbitrarily nested array types as well, as long as they are concrete array types and not generic array types. Note that by the JLS direct bounds cannot be array types (i.e. `Foo<T extends SomeType[]>` is not a valid signature). Signed-off-by: Peter Gafert <[email protected]>
We can now handle generic array types like `T[][]` in `Foo<T, A extends List<T[][]>>`. Note that a generic array type cannot be a toplevel bound of a generic type, but can only appear as part of the nested type signature. Signed-off-by: Peter Gafert <[email protected]>
Once again surprising, but for sufficiently many classes it has a noticeable performance impact to use `String.format(..)` instead of `StringBuilder` (which constant string concatenation will be compiled to in the byte code for all non-ancient JDK versions I know). Since we create the formatted source code location for every class in the import this scales linear with the number of classes. Signed-off-by: Peter Gafert <[email protected]>
To keep the required adjustments of JLS links in Javadoc low, we decided to always link to the latest LTS JLS version where possible, instead of the latest available JLS version. If we need a concept only present in a newer JLS it is okay to simply link to the current JLS. When a new JLS is then released we will update all the links in one shot, including the ones that temporarily had to link to a newer version. Signed-off-by: Peter Gafert <[email protected]>
f49af67
to
bbfd4d5
Compare
This is the next step of #398. It will make the imported type parameters of `JavaClass` widely useful by adding type parameter dependencies (e.g. `Bar` for `class Foo<T extends Bar>`) to the `JavaClass.directDependencies{From/To}Self`. This way type parameter dependencies will now cause violations in all dependency based `ArchRules` like `LayeredArchitecture` or any `classes()...dependOn...()` fluent API methods.
This will solve a part of #115 and add support for generic types to `JavaClass`. It will still not be possible to query superclass or interface type parameters, but it will add support for arbitrarily nested type signatures of `JavaClass` itself, like ``` class Foo<T extends Serializable, U extends List<? super Map<? extends Bar, T[][]>>, V extends T> {} ``` Issue: #115
This is the next step of #398. It will make the imported type parameters of `JavaClass` widely useful by adding type parameter dependencies (e.g. `Bar` for `class Foo<T extends Bar>`) to the `JavaClass.directDependencies{From/To}Self`. This way type parameter dependencies will now cause violations in all dependency based `ArchRules` like `LayeredArchitecture` or any `classes()...dependOn...()` fluent API methods.
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3.6.0 to 3.8.0. from [actions/setup-java's releases](https://github.com/actions/setup-java/releases): > # v3.8.0 > > In scope of this release we added logic to pass the token input through on GHES for Microsoft Build of OpenJDK ([actions/setup-java#395](https://github-redirect.dependabot.com/actions/setup-java/pull/395)) and updated [minimatch](https://github-redirect.dependabot.com/actions/setup-java/pull/413) dependency. Commits * [`c3ac5dd`](actions/setup-java@c3ac5dd) Revert "Add support for Oracle JDK ([#401](https://github-redirect.dependabot.com/actions/setup-java/issues/401))" ([#421](https://github-redirect.dependabot.com/actions/setup-java/issues/421)) * [`dcd29da`](actions/setup-java@dcd29da) Fix typo in README.md ([#419](https://github-redirect.dependabot.com/actions/setup-java/issues/419)) * [`19eeec5`](actions/setup-java@19eeec5) Update to latest `actions/publish-action` ([#411](https://github-redirect.dependabot.com/actions/setup-java/issues/411)) * [`bd7e5d2`](actions/setup-java@bd7e5d2) Update minimatch to 3.1.2 ([#413](https://github-redirect.dependabot.com/actions/setup-java/issues/413)) * [`6cdf39a`](actions/setup-java@6cdf39a) Add support for Oracle JDK ([#401](https://github-redirect.dependabot.com/actions/setup-java/issues/401)) * [`7db6b45`](actions/setup-java@7db6b45) Eclipse Temurin instead of Adopt OpenJDK ([#398](https://github-redirect.dependabot.com/actions/setup-java/issues/398)) * [`bf2f02c`](actions/setup-java@bf2f02c) Pass the token input through on GHES for Microsoft Build of OpenJDK ([#395](https://github-redirect.dependabot.com/actions/setup-java/issues/395)) * See full diff in [compare view](actions/setup-java@v3.6.0...v3.8.0)
This will solve a part of #115 and add support for generic types to
JavaClass
. It will still not be possible to query superclass or interface type parameters, but it will add support for arbitrarily nested type signatures ofJavaClass
itself, likeIssue: #115