-
Notifications
You must be signed in to change notification settings - Fork 357
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 opt.map(type::method) pattern. (#6370)
- Loading branch information
Showing
7 changed files
with
202 additions
and
3 deletions.
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
...ker/src/main/java/org/checkerframework/checker/optional/OptionalAnnotatedTypeFactory.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,133 @@ | ||
package org.checkerframework.checker.optional; | ||
|
||
import com.sun.source.tree.ExpressionTree; | ||
import com.sun.source.tree.MemberReferenceTree; | ||
import com.sun.source.tree.MethodInvocationTree; | ||
import com.sun.source.tree.Tree; | ||
import com.sun.source.tree.Tree.Kind; | ||
import java.util.Collection; | ||
import java.util.function.Function; | ||
import javax.lang.model.element.AnnotationMirror; | ||
import javax.lang.model.element.ElementKind; | ||
import javax.lang.model.element.ExecutableElement; | ||
import org.checkerframework.checker.optional.qual.Present; | ||
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; | ||
import org.checkerframework.common.basetype.BaseTypeChecker; | ||
import org.checkerframework.framework.flow.CFAbstractAnalysis; | ||
import org.checkerframework.framework.flow.CFStore; | ||
import org.checkerframework.framework.flow.CFTransfer; | ||
import org.checkerframework.framework.flow.CFValue; | ||
import org.checkerframework.framework.type.AnnotatedTypeMirror; | ||
import org.checkerframework.javacutil.AnnotationBuilder; | ||
import org.checkerframework.javacutil.TreeUtils; | ||
|
||
/** OptionalAnnotatedTypeFactory for the Optional Checker. */ | ||
public class OptionalAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { | ||
|
||
/** The element for java.util.Optional.map(). */ | ||
private final ExecutableElement optionalMap; | ||
|
||
/** The @{@link Present} annotation. */ | ||
protected final AnnotationMirror PRESENT = AnnotationBuilder.fromClass(elements, Present.class); | ||
|
||
/** | ||
* Creates an OptionalAnnotatedTypeFactory. | ||
* | ||
* @param checker the Optional Checker associated with this type factory | ||
*/ | ||
public OptionalAnnotatedTypeFactory(BaseTypeChecker checker) { | ||
super(checker); | ||
postInit(); | ||
optionalMap = TreeUtils.getMethodOrNull("java.util.Optional", "map", 1, getProcessingEnv()); | ||
} | ||
|
||
@Override | ||
public AnnotatedTypeMirror getAnnotatedType(Tree tree) { | ||
AnnotatedTypeMirror result = super.getAnnotatedType(tree); | ||
optionalMapNonNull(tree, result); | ||
return result; | ||
} | ||
|
||
/** | ||
* If {@code tree} is a call to {@link java.util.Optional#map(Function)} whose argument is a | ||
* method reference, then this method adds {@code @Present} to {@code type} if the following is | ||
* true: | ||
* | ||
* <ul> | ||
* <li>The type of the receiver to {@link java.util.Optional#map(Function)} is {@code @Present}, | ||
* and | ||
* <li>{@link #returnHasNullable(MemberReferenceTree)} returns false. | ||
* </ul> | ||
* | ||
* @param tree a tree | ||
* @param type the type of the tree, which may be side-effected by this method | ||
*/ | ||
private void optionalMapNonNull(Tree tree, AnnotatedTypeMirror type) { | ||
if (!TreeUtils.isMethodInvocation(tree, optionalMap, processingEnv)) { | ||
return; | ||
} | ||
MethodInvocationTree mapTree = (MethodInvocationTree) tree; | ||
ExpressionTree argTree = mapTree.getArguments().get(0); | ||
if (argTree.getKind() == Kind.MEMBER_REFERENCE) { | ||
MemberReferenceTree memberReferenceTree = (MemberReferenceTree) argTree; | ||
AnnotatedTypeMirror optType = getReceiverType(mapTree); | ||
if (optType == null || !optType.hasEffectiveAnnotation(Present.class)) { | ||
return; | ||
} | ||
if (!returnHasNullable(memberReferenceTree)) { | ||
// The method still could have a @PolyNull on the return and might return null. | ||
// If @PolyNull is the primary annotation on the parameter and not on any type arguments or | ||
// array elements, then it is still safe to mark the optional type as present. | ||
// TODO: Add the check for poly null on arguments. | ||
type.replaceAnnotation(PRESENT); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Returns true if the return type of the function type of {@code memberReferenceTree} is | ||
* annotated with {@code @Nullable}. | ||
* | ||
* @param memberReferenceTree a member reference | ||
* @return true if the return type of the function type of {@code memberReferenceTree} is | ||
* annotated with {@code @Nullable} | ||
*/ | ||
private boolean returnHasNullable(MemberReferenceTree memberReferenceTree) { | ||
if (TreeUtils.MemberReferenceKind.getMemberReferenceKind(memberReferenceTree) | ||
.isConstructorReference()) { | ||
return false; | ||
} | ||
ExecutableElement memberReferenceFuncType = TreeUtils.elementFromUse(memberReferenceTree); | ||
if (memberReferenceFuncType.getEnclosingElement().getKind() == ElementKind.ANNOTATION_TYPE) { | ||
// Annotation element accessor are always non-null; | ||
return false; | ||
} | ||
|
||
if (!checker.hasOption("optionalMapAssumeNonNull")) { | ||
return true; | ||
} | ||
return containsNullable(memberReferenceFuncType.getAnnotationMirrors()) | ||
|| containsNullable(memberReferenceFuncType.getReturnType().getAnnotationMirrors()); | ||
} | ||
|
||
/** | ||
* Returns true if {@code annos} contains a nullable annotation. | ||
* | ||
* @param annos a collection of annotations | ||
* @return true if {@code annos} contains a nullable annotation | ||
*/ | ||
private boolean containsNullable(Collection<? extends AnnotationMirror> annos) { | ||
for (AnnotationMirror anno : annos) { | ||
if (anno.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public CFTransfer createFlowTransferFunction( | ||
CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) { | ||
return new OptionalTransfer(analysis); | ||
} | ||
} |
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
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
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
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,16 @@ | ||
import java.math.BigInteger; | ||
import java.util.Optional; | ||
|
||
class MapNoNewNull { | ||
|
||
@SuppressWarnings("optional.parameter") | ||
void m(Optional<Digits> digitsAnnotation) { | ||
if (digitsAnnotation.isPresent()) { | ||
BigInteger maxValue = digitsAnnotation.map(Digits::integer).map(BigInteger::valueOf).get(); | ||
} | ||
} | ||
} | ||
|
||
@interface Digits { | ||
public int integer(); | ||
} |
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,34 @@ | ||
import java.util.Optional; | ||
import org.checkerframework.checker.nullness.qual.Nullable; | ||
import org.checkerframework.checker.nullness.qual.PolyNull; | ||
import org.checkerframework.checker.optional.qual.Present; | ||
|
||
public class OptionalMapMethodReference { | ||
Optional<String> getString() { | ||
return Optional.of(""); | ||
} | ||
|
||
@Present Optional<Integer> method() { | ||
Optional<String> o = getString(); | ||
@Present Optional<Integer> oInt; | ||
if (o.isPresent()) { | ||
// :: error: (assignment) | ||
oInt = o.map(this::convertNull); | ||
oInt = o.map(this::convertPoly); | ||
return o.map(this::convert); | ||
} | ||
return Optional.of(0); | ||
} | ||
|
||
@Nullable Integer convertNull(String s) { | ||
return null; | ||
} | ||
|
||
@PolyNull Integer convertPoly(@PolyNull String s) { | ||
return null; | ||
} | ||
|
||
Integer convert(String s) { | ||
return 0; | ||
} | ||
} |
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