forked from TNG/ArchUnit
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add support for accesses from lambdas TNG#847
For lambdas in Java code (e.g. `() -> "some value"`) the compiler adds a synthetic static method to the Java class that follows the naming pattern `lambda$declaringMethod$42`. It also adds an `invokeDynamic` instruction that creates the link from the method that declares the lambda to this static method. So far we ignore those `invokeDynamic` instructions for lambda cases. On the other hand we treat the synthetic lambda method like an ordinary method. This can lead to confusing behavior, e.g. ``` Supplier<SomeObject> call() { return () -> new SomeObject(); } ``` will now create a `JavaConstructorCall` that originates from some method `lambda$call$0()`, while there will be no trace about any constructor call from within the method `call()`. We decided that for a typical user the simplest and likely most appropriate behavior will be to treat this constructor call from within the lambda as a `JavaConstructorCall` from `call()` to `SomeObject.<init>()` (even though technically it is something different, since the call to `new SomeObject()` will in this case not happen on invocation time of the method, but on invocation time of the return value). From an architecture test point of view the relevant information will likely be, that there is a dependency from `call()` to `new SomeObject()`, which is exactly what this way of treating the call from the lambda will provide. Thus, to improve this we will track all `invokeDynamic` instructions and then "merge" these with the accesses from synthetic lambda methods into more appropriate accesses from the code unit declaring the lambda to the target that the synthetic lambda method targets.
- Loading branch information
Showing
46 changed files
with
1,206 additions
and
606 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 0 additions & 95 deletions
95
.../src/jdk9test/java/com/tngtech/archunit/core/domain/AccessTargetNewerJavaVersionTest.java
This file was deleted.
Oops, something went wrong.
71 changes: 0 additions & 71 deletions
71
archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/DependencyReferencesTest.java
This file was deleted.
Oops, something went wrong.
102 changes: 0 additions & 102 deletions
102
archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/JavaClassReferencesTest.java
This file was deleted.
Oops, something went wrong.
40 changes: 40 additions & 0 deletions
40
...gtech/archunit/core/importer/ClassFileImporterCodeUnitReferencesNewerJavaVersionTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.tngtech.archunit.core.importer; | ||
|
||
import java.util.function.Supplier; | ||
|
||
import com.tngtech.archunit.core.domain.JavaClasses; | ||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
@RunWith(DataProviderRunner.class) | ||
public class ClassFileImporterCodeUnitReferencesNewerJavaVersionTest { | ||
/** | ||
* A local class constructor obtains extra parameters from the outer scope that the compiler transparently adds | ||
* to the byte code. A reference to this local constructor will then always be translated to a lambda call. | ||
* Thus, in this case we do not expect a constructor reference. | ||
* | ||
* Note that this actually does not compile with JDK 8 | ||
*/ | ||
@Test | ||
public void does_not_import_local_constructor_references() { | ||
@SuppressWarnings("unused") | ||
class ReferencedTarget { | ||
ReferencedTarget() { | ||
} | ||
} | ||
@SuppressWarnings("unused") | ||
class Origin { | ||
void referencesConstructor() { | ||
Supplier<ReferencedTarget> a = ReferencedTarget::new; | ||
} | ||
} | ||
|
||
JavaClasses javaClasses = new ClassFileImporter().importClasses(Origin.class, ReferencedTarget.class); | ||
|
||
assertThat(javaClasses.get(Origin.class).getMethod("referencesConstructor").getConstructorReferencesFromSelf()).isEmpty(); | ||
assertThat(javaClasses.get(ReferencedTarget.class).getConstructor(ClassFileImporterCodeUnitReferencesNewerJavaVersionTest.class).getReferencesToSelf()).isEmpty(); | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
...m/tngtech/archunit/core/importer/ClassFileImporterLambdaAccessesNewerJavaVersionTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.tngtech.archunit.core.importer; | ||
|
||
import java.util.Set; | ||
import java.util.function.Supplier; | ||
|
||
import com.tngtech.archunit.core.domain.JavaAccess; | ||
import com.tngtech.archunit.core.domain.JavaClasses; | ||
import com.tngtech.archunit.core.domain.JavaConstructorCall; | ||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
|
||
import static com.google.common.collect.Iterables.getOnlyElement; | ||
import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; | ||
import static com.tngtech.archunit.testutil.Assertions.assertThatAccess; | ||
import static java.util.stream.Collectors.toSet; | ||
|
||
@RunWith(DataProviderRunner.class) | ||
public class ClassFileImporterLambdaAccessesNewerJavaVersionTest { | ||
/** | ||
* This is a special case: For local constructors the Java compiler actually adds a lambda calling the constructor | ||
* for a constructor reference. Since we do not distinguish between calls from within a lambda and outside it, | ||
* this will lead to such a constructor reference being reported as a constructor call. | ||
* | ||
* Note that this actually does not compile with JDK 8 | ||
*/ | ||
@Test | ||
public void imports_constructor_reference_to_local_class_from_lambda_without_parameter_as_direct_call() { | ||
class Target { | ||
} | ||
|
||
@SuppressWarnings("unused") | ||
class Caller { | ||
Supplier<Supplier<Target>> call() { | ||
return () -> Target::new; | ||
} | ||
} | ||
|
||
JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); | ||
JavaConstructorCall call = getOnlyElement( | ||
filterOriginByName(classes.get(Caller.class).getConstructorCallsFromSelf(), "call")); | ||
|
||
assertThatAccess(call).isFrom("call").isTo(Target.class, CONSTRUCTOR_NAME, getClass()); | ||
} | ||
|
||
private <ACCESS extends JavaAccess<?>> Set<ACCESS> filterOriginByName(Set<ACCESS> calls, String methodName) { | ||
return calls.stream() | ||
.filter(call -> call.getOrigin().getName().equals(methodName)) | ||
.collect(toSet()); | ||
} | ||
} |
Oops, something went wrong.