Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
1718f43
Update sideeffectfree javadoc.
smillst Sep 8, 2026
84a53cd
Document purity of lambdas and functional-interface parameters
smillst Sep 8, 2026
fc38f89
Revert "Update sideeffectfree javadoc."
smillst Sep 8, 2026
f0aa2a8
Merge ../checker-framework-branch-master into siddeffectfree-spec
mernst Sep 10, 2026
fb2d3de
Edits
mernst Sep 10, 2026
d44bde9
Tweaks.
smillst Sep 14, 2026
39ce816
Merge branch 'master' into siddeffectfree-spec
mernst Sep 15, 2026
9d18bab
Check functional-interface arguments of side-effect-free methods
smillst Sep 16, 2026
4ab0231
Fix holes and false positives in the functional-argument check
smillst Sep 16, 2026
d2661ba
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Sep 16, 2026
92e3d23
Check each result expression of a conditional or switch argument
smillst Sep 16, 2026
6d5fa18
Do not query the members of the null type
smillst Sep 16, 2026
e39d069
Merge branch 'master' into side-effect-free-functional-params
smillst Sep 17, 2026
d26adfb
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Sep 17, 2026
3f8af7c
Suppress Interning Checker warnings on Element identity comparisons
smillst Sep 17, 2026
451c1ab
Do not infer purity that would constrain functional-interface arguments
smillst Sep 17, 2026
928b37f
Keep refinements in a lambda that calls a functional-interface parameter
smillst Sep 17, 2026
ba26020
Check a method reference like a lambda that calls the referenced method
smillst Sep 17, 2026
d0f2701
Check the arguments passed to a varargs functional-interface parameter
smillst Sep 17, 2026
66e4fd9
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Sep 18, 2026
3163f81
Apply the purity assumptions to a method with no purity annotation
smillst Sep 18, 2026
2dc8e1b
Apply the purity assumptions to a functional argument with no purity …
smillst Sep 18, 2026
bffe864
Merge branch 'purity-assume-unannotated' into side-effect-free-functi…
smillst Sep 18, 2026
f07786e
Adapt tests to the purity annotations on java.util.Comparator
smillst Sep 18, 2026
f8f578b
Add some purity annotations.
smillst Sep 21, 2026
b6349bb
Remove change log entry.
smillst Sep 21, 2026
09cd558
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Sep 21, 2026
2a83216
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Sep 22, 2026
43b51bb
Fix two compilation errors
smillst Sep 22, 2026
a81d884
Require determinism of a void callback that is not side-effect-free
smillst Sep 22, 2026
db685ba
Ignore a cast of a functional argument that denotes code
smillst Sep 22, 2026
8d76d27
Do not infer a purity kind that becomes required once another is disc…
smillst Sep 22, 2026
d3faa74
Fix nullness errors in purity inference and functional-argument checks
smillst Sep 22, 2026
77b71f4
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Sep 22, 2026
0702c59
Do not apply -Aassume* options when computing purity kinds safe to infer
smillst Sep 23, 2026
93e66e9
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Sep 23, 2026
f95bdb7
Do not expect JavaParser nodes for a cast of a method reference
smillst Sep 23, 2026
a7a2cb2
Merge branch 'ajava-cast-method-reference' into side-effect-free-func…
smillst Sep 23, 2026
b9a395b
Document functional-parameter rules for all purity annotations
smillst Sep 23, 2026
014046f
Suppress warnings.
smillst Sep 23, 2026
46c83bf
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Sep 23, 2026
dd494d4
Merge branch 'ajava-cast-method-reference' into side-effect-free-func…
smillst Sep 23, 2026
d0e37b2
Merge branch 'side-effect-free-functional-params' of github.com:smill…
smillst Sep 23, 2026
275c2eb
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Sep 23, 2026
2fbd806
Restrict functional-parameter purity to what every overridden declara…
smillst Sep 23, 2026
dfafa84
Assert non-nullness in `functionalParameterPurity`
smillst Sep 23, 2026
9e0e945
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Sep 23, 2026
d08a1f2
Use renamed purity message keys in functional-parameter tests
smillst Sep 23, 2026
5055d89
Update framework/src/main/java/org/checkerframework/common/basetype/B…
smillst Sep 24, 2026
63f86b9
Update docs/manual/purity-checker.tex
smillst Sep 24, 2026
ce9c27a
Update framework/tests/all-systems/Issue6874.java
smillst Sep 24, 2026
e514fc1
Merge branch 'master' into side-effect-free-functional-params
smillst Sep 24, 2026
fbc06a9
Note that purity errors for lambdas and functional arguments require …
smillst Sep 24, 2026
040e1e7
Merge branch 'master' into side-effect-free-functional-params
smillst Sep 24, 2026
55dccfd
Merge branch 'master' into side-effect-free-functional-params
mernst Sep 29, 2026
7b5da5a
Add test: inherited method implementing an interface method with weak…
mernst Sep 30, 2026
0313140
Merge branch 'master' into side-effect-free-functional-params
smillst Sep 30, 2026
d77bbdc
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Oct 2, 2026
09bc9ef
Let an override assume what its own purity annotation requires of fun…
smillst Oct 2, 2026
f52852c
Check a functional subinterface argument against the method that impl…
smillst Oct 2, 2026
8f16515
Check code cast to a functional subinterface against the method that …
smillst Oct 2, 2026
8078b4e
Check an argument to a class type variable parameter against its type…
smillst Oct 2, 2026
bbc7530
Add a failing test for an inherited implementation of a generic inter…
smillst Oct 2, 2026
256585c
Merge remote-tracking branch 'origin/master' into side-effect-free-fu…
smillst Oct 2, 2026
b3bb4af
Merge branch 'master' into side-effect-free-functional-params
smillst Oct 5, 2026
8063daa
Check an argument to a method type variable parameter against its typ…
smillst Oct 5, 2026
403df4e
Do not infer purity for a method with a parameter of an Object-bounde…
smillst Oct 6, 2026
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
4 changes: 4 additions & 0 deletions checker/tests/nullness/java8/Issue496.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
// https://github.com/typetools/checker-framework/issues/496

import java.util.Optional;
import org.checkerframework.dataflow.qual.SideEffectFree;

public class Issue496 {

public static class Entity<T> {
public final T value;
public final Class<T> cls;

// Optional.map is @SideEffectFree, so the mapper below must be too, and calling this
// constructor is side-effect-free only if it says so.
@SideEffectFree
public Entity(T value, Class<T> cls) {
this.value = value;
this.cls = cls;
Expand Down
28 changes: 28 additions & 0 deletions checker/tests/optional/LambdaMethodCallValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,34 @@ void test7(OptContainer container) {
}
}

// A lambda's body may call a functional-interface parameter of the enclosing method. The
// caller of that method was required to pass pure code, so the call does not discard
// refinements. (This test does not use -AcheckPurityAnnotations, so the bodies below are not
// checked against their purity annotations.)
@Pure
String test8(OptContainer container, List<String> strs, Consumer<String> op) {
if (container.getOpt().isPresent()) {
strs.forEach(
s -> {
container.getOpt().get(); // OK
op.accept(s);
});
}
return "";
}

/** An unannotated method promises nothing about the code it is passed. */
void test9(OptContainer container, List<String> strs, Consumer<String> op) {
if (container.getOpt().isPresent()) {
strs.forEach(
s -> {
// :: error: [method.invocation]
container.getOpt().get(); // Not ok: the lambda body is not pure
op.accept(s);
});
}
}

class OptContainer {

@SuppressWarnings("optional:field") // Don't care about this warning, unrelated to the test case
Expand Down

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ single-page version, there is a

### Changes for type system implementers

`PurityChecker.checkPurity()` takes two new arguments: the method declaration
that lexically encloses the statement being checked (or null, as for an
arbitrary expression) and the processing environment.

Comment thread
mernst marked this conversation as resolved.
`JavaParserUtil`: moved `DEFAULT_LANGUAGE_LEVEL`, `parseCompilationUnit()`,
`parseStubUnit()`, and `parseExpression()` into new class `StaticJavaParserUtil`.

Expand Down
194 changes: 180 additions & 14 deletions docs/manual/purity-checker.tex
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
\chapterAndLabel{Purity Checker}{purity-checker}

The Purity Checker identifies methods that have no side effects, that
return the same value each time they are called on the same argument, or both.
A \emph{side-effect-free} method is one that has no externally-visible side
effects: it changes no object that exists when it is called, except in ways
that no caller can observe, such as updating a private cache.
A \emph{deterministic} method returns the same value each time it is called
on the same arguments.
Comment thread
smillst marked this conversation as resolved.
A \emph{pure} method is both side-effect-free and deterministic.
The Purity Checker can check and infer all three types of methods,
and ``purity'' refers to all concepts.

Purity analysis aids type refinement (Section~\ref{type-refinement}).
All checkers utilize purity annotations to aid type refinement
(Section~\ref{type-refinement}). By default, purity annotations are
trusted, not checked.

All checkers utilize purity annotations on called methods.
You do not need to run the Purity Checker directly.
However, you may run just the Purity Checker by
supplying the following command-line option to javac:
To check purity annotations, pass \<-AcheckPurityAnnotations> to any
checker.
To run \emph{only} the Purity Checker (this is uncommon),
supply the following command-line option to javac:
\code{-processor org.checkerframework.framework.util.PurityChecker}.
That implies \<-AcheckPurityAnnotations>, so you do not need to supply
both options.

The Checker Framework can infer purity annotations.
If you supply the command-line option \<-AsuggestPureMethods>, then the
Checker Framework will suggest methods that can be marked as
To infer purity annotations,
supply the command-line option \<-AsuggestPureMethods>.
The Checker Framework will suggest methods that can be marked as
\<@SideEffectFree>, \<@Deterministic>, or \<@Pure>.
In addition, such suggestions are output by \<-Ainfer> and when using
whole-program inference.
Expand Down Expand Up @@ -44,10 +52,167 @@
\end{description}


\sectionAndLabel{Lambdas, method references, and functional interfaces}{purity-functional-interfaces}

Creating a lambda, or declaring a local or anonymous class, is not itself a
side effect. A lambda expression evaluates to an object but does not run the code
in the lambda's body. A side-effect-free method may therefore create a lambda
whose body has side effects:

\begin{Verbatim}
@SideEffectFree
Runnable makeRunnable() {
return () -> count++; // OK: creating the lambda has no side effect
}
\end{Verbatim}

\noindent
The effects of the body occur where the functional method is invoked, and
each such invocation is checked on its own.

The body of a lambda is checked against the purity annotations on the
functional interface method that the lambda implements, not against those on
the method that creates the lambda. The errors shown in this section are
reported only when purity annotations are checked, such as with
\<-AcheckPurityAnnotations>; they are not reported by default or by
\<-Ainfer>. For example:

\begin{Verbatim}
@FunctionalInterface
interface PureSupplier {
@SideEffectFree String get();
}

PureSupplier s1 = () -> "hello"; // OK
PureSupplier s2 = () -> { count++; return ""; }; // error: the body has a side effect
Comment thread
coderabbitai[bot] marked this conversation as resolved.
\end{Verbatim}


\subsectionAndLabel{Functional-interface parameters of methods with purity annotations}{purity-functional-interface-parameters}

A method annotated with a purity annotation may call the functional method of
its own functional-interface parameters. Such a call is treated as having the
purity that the method promises:

\begin{itemize}
\item
In a \refqualclass{dataflow/qual}{SideEffectFree} method, the call is
treated as side-effect-free.
\item
In a \refqualclass{dataflow/qual}{Deterministic} method, the call is
treated as deterministic.
\item
In a \refqualclass{dataflow/qual}{Pure} method, the call is treated as
pure.
\iffalse
\item
In a \refqualclass{dataflow/qual}{SideEffectsOnly} method, the call is
treated as side-effect-free. The method itself may modify the expressions
listed in its annotation, but the code it is handed may not modify anything.
Comment thread
smillst marked this conversation as resolved.
\fi
\end{itemize}

\begin{Verbatim}
@SideEffectFree
int m(Function<String, Integer> f, String s) {
return f.apply(s); // permitted: apply is treated as side-effect-free
}
\end{Verbatim}

The assumption applies only when the parameter is effectively final and the
functional method is called directly on the parameter. It does not apply to a
call through a local variable or field that aliases the parameter, nor to a
method of the parameter's type other than its functional method, such as
\<Function.andThen>:

\begin{Verbatim}
@SideEffectFree
int m2(Function<String, Integer> f, String s) {
Function<String, Integer> g = f;
return g.apply(s); // error: g is not a parameter
}
\end{Verbatim}

The assumption also does not apply if the method overrides a method that
makes the same promise but whose parameter at that position has a type
variable as its type, because a call through the overridden method relies on
the promise without checking the argument when the type variable is not
instantiated with a functional interface at the call. An overridden method that makes
no promise does not limit the assumption: a call through it may pass an
argument with side effects, but that call does not rely on the purity of the
method either.

The exception is code that calls the functional method of one of its own
functional-interface parameters, which relies on the call's purity even if
the functional method makes no promise. Suppose that class \<C> implements
interface \<I>, and \<C.apply> is \<@SideEffectFree> but \<I.apply> is not.
Then passing a \<C> where an \<I> is required, to a parameter of a
\<@SideEffectFree> method, is an error if \<C.apply> assumes that its own
functional-interface argument is side-effect-free, because a call through
\<I.apply> does not check that argument. The same holds for a method
reference to \<C.apply>, which is also an error wherever the functional
method that it implements makes the promise without checking the argument.

At each call to such a method, every argument passed to a functional-interface
parameter must have the purity that the method promises (side-effect-free for
Comment thread
smillst marked this conversation as resolved.
a \<@SideEffectsOnly> method). For a varargs parameter whose element type is
a functional interface, such as \<Runnable...>, each argument that the call
passes as an element of the array is checked, as is each initializer of an
array literal such as \<new Runnable[] \{...\}> that the call passes. A
parameter whose type is a type variable is a functional-interface parameter
at a call that instantiates the type variable with a functional interface.
For \<T> in \<@SideEffectFree int apply(T t)> in \code{interface I<T>}, a
call through \code{I<Supplier<Integer>>} checks its argument against
\<Supplier.get>. Likewise, a call \code{List.<Runnable>of(() -> count++)}
that instantiates \<E> in \<List.of(E)> with \<Runnable> checks its argument against
\<Runnable.run>: a method that is passed a functional-interface argument is
assumed to call it. How the argument is checked depends on its form:

\begin{itemize}
\item
A method reference is checked via the referenced method, which must have
the required purity annotations.
\item
A lambda is checked via its body, which must have the required purity.
\item
Any other argument is checked via the functional method of the argument's
declared type, which must have the required purity annotations.
\end{itemize}

\begin{Verbatim}
m(String::length, "hi"); // OK: String.length() is side-effect-free
m(s -> s.length(), "hi"); // OK: the lambda body is side-effect-free
m(s -> count++, "hi"); // error: the lambda body has a side effect

void caller(Function<String, Integer> g) {
m(g, "hi"); // error: [purity.functional.argument] Function.apply is not @SideEffectFree
}
\end{Verbatim}

If the functional method returns \<void>, a \<@Pure> method requires only
that the argument be side-effect-free, because a side-effect-free call that
returns no value is deterministic.

%% TODO: we could implement a type system for functional interfaces with a
%% qualifier \<@SideEffectFreeFunctionalInterface>. Then the default for a
%% \<@SideEffectFree> method would be:
%For a side-effect-free method, the default type is
%\<@SideEffectFreeFunctionalInterface> for any parameter that is a functional
%interface. That is, the following two declarations are equivalent:
%
%\begin{Verbatim}
% @SideEffectFree
% int apply(Function<String, Integer> f, String s) { ... }
%
% @SideEffectFree
% int apply(@SideEffectFreeFunctionalInterface Function<String, Integer> f, String s) { ... }
%\end{Verbatim}


\sectionAndLabel{Purity annotations are trusted}{purity-trusted}

By default, purity annotations are trusted. Purity annotations on called
methods affect type-checking of client code. However, you can make a
By default, purity annotations are trusted by every checker built with the
Checker Framework, and the Purity Checker is not run. However, you can make a
mistake
%
by writing \refqualclass{dataflow/qual}{SideEffectFree} on the
Expand Down Expand Up @@ -141,5 +306,6 @@
\end{itemize}


%% LocalWords: AsuggestPureMethods AcheckPurityAnnotations
%% LocalWords: AsuppressWarnings AassumeSideEffectFree
%% LocalWords: AsuggestPureMethods AcheckPurityAnnotations Ainfer
%% LocalWords: AsuppressWarnings AassumeSideEffectFree SideEffectsOnly
% LocalWords: TODO AassumeDeterministic AassumePure AassumePureGetters
Loading
Loading