RFR: 8348611: Eliminate DeferredLintHandler and emit warnings after attribution [v11]
Stephan Schroevers
duke at openjdk.org
Sun Sep 21 15:05:35 UTC 2025
On Thu, 14 Aug 2025 04:00:12 GMT, Archie Cobbs <acobbs at openjdk.org> wrote:
>> This is a cleanup/refactoring of how lint warnings are logged and `@SuppressWarnings` annotations applied.
>>
>> A central challenge with lint warnings is that warnings can be generated during any compiler phase, but whether a particular lint warning is suppressed via `@SuppressWarnings` can't be known until after attribution. For example, the parser doesn't have enough information to interpret and apply `@SuppressWarnings("text-blocks")` to text blocks, or `@SuppressWarnings("preview")` to preview lexical features; instead, the `DeferredLintHandler` is used to workaround this limitation.
>>
>> In addition, several other factors complicate things:
>> * Knowing the current applicable `Lint` instance requires manually tracking it with each declaration visited and applying/removing the `@SuppressWarnings` annotation there, if any
>> * Some warnings are "suppressibly mandatory" (i.e., they are _emitted if not suppressed_ instead of _emitted if enabled_)
>> * Some warnings are "unsuppressibly mandatory" (e.g., the "internal proprietary API" warning)
>> * Some mandatory warnings are _aggregated_ into notes that are emitted at the end of compilation when not enabled
>> * Some warnings are _lint_ warnings, with a corresponding lint category, while others are just "plain" warnings
>> * Some lint warnings are suppressible via `@SuppressWarnings`, while others are only suppressible via `-Xlint:-foo` flags
>> * Speculative compilation requires holding log messages in purgatory until the speculation resolves, after which they are then either discarded or emitted. But this creates a tricky interaction with `DeferredLintHandler` because even after speculation is complete, we may still not yet know whether a warning should be suppressed.
>>
>> Previously the logic to get all of this right was non-obviously woven around the code base. In particular, you needed to know somehow whether or not to use `DeferredLintHandler`, and in what "mode".
>>
>> The overall goal of this PR is to simplify usage so that **no matter where you are in the compiler, you can just invoke `log.warning()` to log a warning** and (mostly) forget about all of the details listed above.
>
> Archie Cobbs has updated the pull request incrementally with one additional commit since the last revision:
>
> Remove unused field from "LintRange" record.
Alright, run the following script in an empty directory to create a `pom.xml` and `src/main/java/com/example/Reproducer.java`:
#!/usr/bin/env bash
set -euo pipefail
# Creates files in the current directory to reproduce the issue.
# This script writes `pom.xml` and `src/main/java/com/example/Reproducer.java`.
mkdir -p src/main/java/com/example
cat > pom.xml <<'POM_EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>reproducer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.42.0</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-bom</artifactId>
<version>3.27.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-recipe-bom</artifactId>
<version>3.14.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java</artifactId>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-templating</artifactId>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<compilerArgs>
<arg>-Xlint:all</arg>
</compilerArgs>
<parameters>true</parameters>
<release>17</release>
<showWarnings>true</showWarnings>
<failOnWarning>true</failOnWarning>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>break-it</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-templating</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
</profiles>
</project>
POM_EOF
cat > src/main/java/com/example/Reproducer.java <<'JAVA_EOF'
package com.example;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import org.assertj.core.api.AbstractThrowableAssert;
final class Reproducer {
// XXX: This rule changes the `Throwable` against which subsequent assertions are made.
static final class AbstractThrowableAssertCauseIsSameAs {
@BeforeTemplate
@SuppressWarnings("deprecation" /* This deprecated API will be rewritten. */)
AbstractThrowableAssert<?, ? extends Throwable> before(
AbstractThrowableAssert<?, ? extends Throwable> throwableAssert, Throwable expected) {
return throwableAssert.hasCauseReference(expected);
}
@AfterTemplate
AbstractThrowableAssert<?, ? extends Throwable> after(
AbstractThrowableAssert<?, ? extends Throwable> throwableAssert, Throwable expected) {
return throwableAssert.cause().isSameAs(expected);
}
}
}
JAVA_EOF
With JDK 25, the build passes when running `mvn clean compile`, but fails when running `mvn clean compile -Pbreak-it`:
...
[INFO] --- compiler:3.14.0:compile (default-compile) @ reproducer ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 1 source file with javac [debug parameters release 17] to target/classes
[INFO] -------------------------------------------------------------
[WARNING] COMPILATION WARNING :
[INFO] -------------------------------------------------------------
[WARNING] /tmp/_/src/main/java/com/example/Reproducer.java:[14,29] hasCauseReference(java.lang.Throwable) in org.assertj.core.api.AbstractThrowableAssert has been deprecated
[INFO] 1 warning
[INFO] -------------------------------------------------------------
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] warnings found and -Werror specified
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
...
With JDK 24 and below (and the latest JDK 26 EA build) the build correctly passes with both commands.
-------------
PR Comment: https://git.openjdk.org/jdk/pull/24584#issuecomment-3316051542
More information about the compiler-dev
mailing list