Files
allwpilib/javacPlugin/src/test/java/org/wpilib/javacplugin/CompileTestUtils.java
Sam Carlberg 8992cf7081 [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")`
2025-11-01 17:27:08 -07:00

49 lines
1.6 KiB
Java

// 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>";
}
}
}