Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import static java.util.stream.Collectors.toList;
import static org.openrewrite.java.tree.J.Wildcard.Bound.Extends;
Expand Down Expand Up @@ -95,7 +96,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex
if (varParam.getTypeExpression() instanceof J.ParameterizedType) {
J.ParameterizedType pt = (J.ParameterizedType) varParam.getTypeExpression();
for (VariantTypeSpec variantTypeSpec : variantTypeSpecs) {
if (variantTypeSpec.hasType(pt)) {
if (variantTypeSpec.hasType(pt) && !isStoredInvariantly(m, varParam)) {
return varParam.withTypeExpression(useDeclarationSiteVariance(pt, variantTypeSpec));
}
}
Expand All @@ -106,6 +107,72 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex
}));
}

private boolean isStoredInvariantly(J.MethodDeclaration method, J.VariableDeclarations parameter) {
if (method.getBody() == null || parameter.getVariables().size() != 1) {
return false;
}
JavaType.Variable parameterType = parameter.getVariables().get(0).getVariableType();
if (parameterType == null) {
return false;
}
return new JavaIsoVisitor<AtomicBoolean>() {
@Override
public J.Assignment visitAssignment(J.Assignment assignment, AtomicBoolean stored) {
J.Assignment visitedAssignment = super.visitAssignment(assignment, stored);
if (TypeUtils.isOfType(visitedAssignment.getVariable().getType(), parameterType.getType()) &&
!references(visitedAssignment.getVariable(), parameterType) &&
directlyReferences(visitedAssignment.getAssignment(), parameterType)) {
stored.set(true);
}
return visitedAssignment;
}

@Override
public J.VariableDeclarations.NamedVariable visitVariable(
J.VariableDeclarations.NamedVariable variable, AtomicBoolean stored) {
J.VariableDeclarations.NamedVariable visitedVariable = super.visitVariable(variable, stored);
J.VariableDeclarations declarations = getCursor().firstEnclosing(J.VariableDeclarations.class);
boolean explicitlyTyped = declarations != null &&
!(declarations.getTypeExpression() instanceof J.Identifier &&
"var".equals(((J.Identifier) declarations.getTypeExpression()).getSimpleName()));
if (explicitlyTyped && visitedVariable.getInitializer() != null &&
TypeUtils.isOfType(visitedVariable.getType(), parameterType.getType()) &&
directlyReferences(visitedVariable.getInitializer(), parameterType)) {
stored.set(true);
}
return visitedVariable;
}
}.reduce(method.getBody(), new AtomicBoolean()).get();
}

private boolean references(Expression expression, JavaType.Variable parameterType) {
return new JavaIsoVisitor<AtomicBoolean>() {
@Override
public J.Lambda visitLambda(J.Lambda lambda, AtomicBoolean found) {
return lambda;
}

@Override
public J.MemberReference visitMemberReference(J.MemberReference memberRef, AtomicBoolean found) {
return memberRef;
}

@Override
public J.Identifier visitIdentifier(J.Identifier identifier, AtomicBoolean found) {
if (parameterType.equals(identifier.getFieldType())) {
found.set(true);
}
return identifier;
}
}.reduce(expression, new AtomicBoolean()).get();
}

private boolean directlyReferences(Expression expression, JavaType.Variable parameterType) {
Expression unwrapped = expression.unwrap();
return unwrapped instanceof J.Identifier &&
parameterType.equals(((J.Identifier) unwrapped).getFieldType());
}

private J.ParameterizedType useDeclarationSiteVariance(J.ParameterizedType pt, VariantTypeSpec spec) {
return pt.withTypeParameters(ListUtils.map(pt.getTypeParameters(), (i, tp) -> {
VariantTypeSpec.Variance variance = spec.getVariances().get(i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,220 @@ public void test(Function<In, Out> f) {
)
);
}
@Test
void doesNotAddVarianceToParameterStoredInvariantly() {
rewriteRun(
//language=java
java(
"""
import java.util.function.Function;

class FieldTest<Input, Output> {
private final Function<Input, Output> mapper;

FieldTest(Function<Input, Output> mapper) {
this.mapper = mapper;
}
}
"""
),
//language=java
java(
"""
import java.util.function.Function;

class LocalTest<Input, Output> {
void test(Function<Input, Output> mapper) {
Function<Input, Output> stored = mapper;
}
}
"""
)
);
}

@Test
void addsVarianceWhenFieldAcceptsIt() {
rewriteRun(
//language=java
java(
"""
import java.util.function.Function;

class Test<Input, Output> {
private final Function<? super Input, ? extends Output> mapper;

Test(Function<Input, Output> mapper) {
this.mapper = mapper;
}
}
""",
"""
import java.util.function.Function;

class Test<Input, Output> {
private final Function<? super Input, ? extends Output> mapper;

Test(Function<? super Input, ? extends Output> mapper) {
this.mapper = mapper;
}
}
"""
)
);
}

@Test
void addsVarianceWhenInvariantFieldStoresAdapter() {
rewriteRun(
//language=java
java(
"""
import java.util.function.Function;

class Test<Input, Output> {
private Function<Input, Output> mapper;

void set(Function<Input, Output> mapper) {
this.mapper = input -> mapper.apply(input);
}

void forward(Function<Input, Output> mapper) {
set(mapper);
}
}
""",
"""
import java.util.function.Function;

class Test<Input, Output> {
private Function<Input, Output> mapper;

void set(Function<? super Input, ? extends Output> mapper) {
this.mapper = input -> mapper.apply(input);
}

void forward(Function<? super Input, ? extends Output> mapper) {
set(mapper);
}
}
"""
)
);
}

@Test
void addsVarianceWhenParameterIsNormalizedInPlace() {
rewriteRun(
//language=java
java(
"""
import java.util.Objects;
import java.util.function.Function;

class Test<Input, Output> {
void use(Function<Input, Output> mapper) {
mapper = Objects.requireNonNull(mapper);
}

void forward(Function<Input, Output> mapper) {
use(mapper);
}
}
""",
"""
import java.util.Objects;
import java.util.function.Function;

class Test<Input, Output> {
void use(Function<? super Input, ? extends Output> mapper) {
mapper = Objects.requireNonNull(mapper);
}

void forward(Function<? super Input, ? extends Output> mapper) {
use(mapper);
}
}
"""
)
);
}

@Test
void addsVarianceWhenParameterIsStoredInVar() {
rewriteRun(
//language=java
java(
"""
import java.util.function.Function;

class Test<Input, Output> {
void use(Function<Input, Output> mapper) {
var stored = mapper;
}

void forward(Function<Input, Output> mapper) {
use(mapper);
}
}
""",
"""
import java.util.function.Function;

class Test<Input, Output> {
void use(Function<? super Input, ? extends Output> mapper) {
var stored = mapper;
}

void forward(Function<? super Input, ? extends Output> mapper) {
use(mapper);
}
}
"""
)
);
}

@Test
void addsVarianceWhenInvariantLocalStoresAdapterResult() {
rewriteRun(
//language=java
java(
"""
import java.util.function.Function;

class Test<Input, Output> {
Function<Input, Output> adapt(Function<? super Input, ? extends Output> mapper) {
return input -> mapper.apply(input);
}

void use(Function<Input, Output> mapper) {
Function<Input, Output> stored = adapt(mapper);
}

void forward(Function<Input, Output> mapper) {
use(mapper);
}
}
""",
"""
import java.util.function.Function;

class Test<Input, Output> {
Function<Input, Output> adapt(Function<? super Input, ? extends Output> mapper) {
return input -> mapper.apply(input);
}

void use(Function<? super Input, ? extends Output> mapper) {
Function<Input, Output> stored = adapt(mapper);
}

void forward(Function<? super Input, ? extends Output> mapper) {
use(mapper);
}
}
"""
)
);
}
}
Loading