mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-24 01:31:46 +00:00
[javac plugin] Add compile-time checks for unsafe or incorrect coroutine usage (#8289)
CoroutineYieldInLoopDetector
This checks for while loops where coroutines are in scope but without calling a blocking method on at least one of those coroutines:
```
drivetrain.run(theCoroutine -> {
while (drivetrain.getDistance() < 10) { // ERROR: "Missing call to `theCoroutine.yield()` inside loop"
drivetrain.setSpeed(1);
}
});
```
Note that, because we assume most looping constructs in commands will use while loops, we don't check for-loops, for-each loops, or do-while loops.
This check can be disabled with `@SuppressWarnings("CoroutineYieldInLoop")`
CodeAfterCoroutineParkDetector
Essentially acts like the Java compiler's check for code after a while (true) loop, but for coroutine.park():
```
drivetrain.run(theCoroutine -> {
drivetrain.setSpeed(1.0);
theCoroutine.park();
drivetrain.setSpeed(0.0); // ERROR: "Unreachable statement: `theCoroutine.park()` will never exit"
});
```
This check can be disabled with `@SuppressWarnings("CodeAfterCoroutinePark")`
IncorrectCoroutineUseDetector
Checks for usage of captured (outer) coroutine parameters and assignments to fields.
```
drivetrain.run(outer -> {
outer.await(arm.run(inner -> {
outer.yield(); // ERROR: "Coroutine `outer` may not be in scope. Consider using `inner`"
}))
});
```
This check can be disabled with `@SuppressWarnings("CoroutineMayNotBeInScope")`
```
private Coroutine coroutineField;
drivetrain.run(co -> coroutineField = co); // ERROR: "Captured coroutines may not be stored in fields"
```
This check can be disabled with `@SuppressWarnings("CoroutineCapture")`
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package org.wpilib.javacplugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
public final class CompileTestUtils {
|
||||
public static final int kJavaVersion = 17;
|
||||
public static final List<Object> kJavaVersionOptions =
|
||||
List.of("-source", kJavaVersion, "-target", kJavaVersion);
|
||||
|
||||
private CompileTestUtils() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source code for a given compiler error.
|
||||
*
|
||||
* @param diagnostic The diagnostic to get the source for.
|
||||
* @return The source code for the given diagnostic.
|
||||
*/
|
||||
public static String getErrorSource(Diagnostic<? extends JavaFileObject> diagnostic) {
|
||||
try (var reader = diagnostic.getSource().openReader(true)) {
|
||||
int sourceLength = (int) (diagnostic.getEndPosition() - diagnostic.getStartPosition() + 1);
|
||||
char[] buf = new char[sourceLength];
|
||||
long skipCnt = reader.skip(diagnostic.getStartPosition());
|
||||
if (skipCnt != diagnostic.getStartPosition()) {
|
||||
// Didn't skip to the expected position; bail
|
||||
return "<unknown source>";
|
||||
}
|
||||
|
||||
int readCnt = reader.read(buf);
|
||||
if (readCnt != sourceLength) {
|
||||
// Didn't read the expected length of text; bail
|
||||
return "<unknown source>";
|
||||
}
|
||||
|
||||
return new String(buf);
|
||||
} catch (IOException e) {
|
||||
return "<unknown source>";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user