Merge branch 'main' into 2027

This commit is contained in:
Peter Johnson
2025-10-06 19:43:02 -07:00
47 changed files with 1439 additions and 195 deletions

View File

@@ -1,72 +0,0 @@
name: Documentation
on: [push, workflow_dispatch]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
BASE_PATH: allwpilib/docs
jobs:
publish:
name: "Documentation - Publish"
runs-on: ubuntu-24.04
if: github.repository == 'wpilibsuite/allwpilib' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
concurrency: ci-docs-publish
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- uses: gradle/actions/wrapper-validation@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
- name: Set environment variables (Development)
run: |
echo "BRANCH=development" >> $GITHUB_ENV
if: github.ref == 'refs/heads/main'
- name: Set environment variables (Tag)
run: |
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
echo "BRANCH=beta" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
- name: Set environment variables (Release)
run: |
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
echo "BRANCH=release" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') && !contains(github.ref, '2027')
- name: Set environment variables (2027)
run: |
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
echo "BRANCH=2027" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '2027')
- name: Build with Gradle
run: ./gradlew docs:generateJavaDocs docs:doxygen -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
- name: Install SSH Client 🔑
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.GH_DEPLOY_KEY }}
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.6.1
with:
ssh-key: true
repository-name: wpilibsuite/wpilibsuite.github.io
branch: allwpilib-${{ env.BRANCH }}
clean: true
single-commit: true
folder: docs/build/docs
- name: Trigger Workflow
uses: actions/github-script@v7
with:
github-token: ${{ secrets.DISPATCH_PAT_TOKEN }}
script: |
github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: 'wpilibsuite.github.io',
workflow_id: 'static.yml',
ref: 'main',
})

View File

@@ -31,6 +31,9 @@ def main():
# Replace GCC warning argument with one Clang recognizes
elif arg == "-Wno-maybe-uninitialized":
out_args.append("-Wno-uninitialized")
# Skip GCC-specific warning argument
elif arg == "-Wno-error=restrict":
pass
else:
out_args.append(arg)

View File

@@ -194,7 +194,7 @@ jobs:
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v2027')
- name: Build with Gradle
run: ./gradlew docs:zipDocs --build-cache -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
run: ./gradlew docs:zipDocs --build-cache -PbuildServer -PdocWarningsAsErrors ${{ env.EXTRA_GRADLE_ARGS }}
env:
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
@@ -203,6 +203,73 @@ jobs:
name: Documentation
path: docs/build/outputs
publish:
name: "Documentation - Publish"
runs-on: ubuntu-22.04
if: github.repository == 'wpilibsuite/allwpilib' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
needs: [build-documentation]
concurrency: ci-docs-publish
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Download docs artifacts
uses: actions/download-artifact@v4
with:
name: Documentation
- name: Make output directories
run: |
mkdir -p docs/tmp/doxygen/html
mkdir -p docs/tmp/javadoc
- name: Extract docs
run: |
unzip _GROUP_edu_wpi_first_wpilibc_ID_documentation_CLS.zip -d docs/tmp/doxygen/html
unzip _GROUP_edu_wpi_first_wpilibj_ID_documentation_CLS.zip -d docs/tmp/javadoc
- name: Set environment variables (Development)
run: |
echo "BRANCH=development" >> $GITHUB_ENV
if: github.ref == 'refs/heads/main'
- name: Set environment variables (Tag)
run: |
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
echo "BRANCH=beta" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
- name: Set environment variables (Release)
run: |
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
echo "BRANCH=release" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') && !contains(github.ref, '2027')
- name: Set environment variables (2027)
run: |
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
echo "BRANCH=2027" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '2027')
- name: Install SSH Client 🔑
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.GH_DEPLOY_KEY }}
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.6.1
with:
ssh-key: true
repository-name: wpilibsuite/wpilibsuite.github.io
branch: allwpilib-${{ env.BRANCH }}
clean: true
single-commit: true
folder: docs/tmp
- name: Trigger Workflow
uses: actions/github-script@v7
with:
github-token: ${{ secrets.DISPATCH_PAT_TOKEN }}
script: |
github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: 'wpilibsuite.github.io',
workflow_id: 'static.yml',
ref: 'main',
})
combine:
name: Combine
needs: [build-docker, build-host, build-documentation]

View File

@@ -123,18 +123,3 @@ jobs:
echo '' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
if: ${{ failure() }}
documentation:
name: "Documentation"
runs-on: ubuntu-24.04
needs: [validation]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
- name: Build with Gradle
run: ./gradlew docs:zipDocs -PbuildServer -PdocWarningsAsErrors ${{ env.EXTRA_GRADLE_ARGS }}

View File

@@ -283,6 +283,7 @@ endif()
set(FILENAME_DEP_REPLACE "get_filename_component(SELF_DIR \"$\{CMAKE_CURRENT_LIST_FILE\}\" PATH)")
set(SELF_DIR "$\{SELF_DIR\}")
set(WPIUNITS_DEP_REPLACE_IMPL "find_dependency(wpiunits)")
set(WPIANNOTATIONS_DEP_REPLACE_IMPL "find_dependency(wpiannotations)")
set(WPIUTIL_DEP_REPLACE "find_dependency(wpiutil)")
set(DATALOG_DEP_REPLACE "find_dependency(datalog)")
add_subdirectory(wpiutil)
@@ -305,6 +306,10 @@ if(WITH_WPIMATH)
add_subdirectory(wpimath)
endif()
if(WITH_JAVA)
add_subdirectory(wpiannotations)
endif()
if(WITH_WPIUNITS AND NOT WITH_WPIMATH)
# In case of building wpiunits standalone
set(WPIUNITS_DEP_REPLACE ${WPIUNITS_DEP_REPLACE_IMPL})

View File

@@ -181,7 +181,11 @@ tasks.register('deployStatic') {
model {
components {
benchmarkCpp(NativeExecutableSpec) {
targetBuildTypes 'debug'
if (project.hasProperty('ciDebugOnly')) {
targetBuildTypes 'debug'
} else {
targetBuildTypes 'release'
}
sources {
cpp {
source {
@@ -235,7 +239,11 @@ model {
}
}
benchmarkCppStatic(NativeExecutableSpec) {
targetBuildTypes 'debug'
if (project.hasProperty('ciDebugOnly')) {
targetBuildTypes 'debug'
} else {
targetBuildTypes 'release'
}
nativeUtils.excludeBinariesFromStrip(it)
sources {
cpp {

View File

@@ -9,5 +9,5 @@ repositories {
}
}
dependencies {
implementation "edu.wpi.first:native-utils:2025.12.1"
implementation "edu.wpi.first:native-utils:2026.0.0"
}

View File

@@ -9,6 +9,7 @@ evaluationDependsOn(':cscore')
evaluationDependsOn(':epilogue-runtime')
evaluationDependsOn(':hal')
evaluationDependsOn(':ntcore')
evaluationDependsOn(':wpiannotations')
evaluationDependsOn(':wpilibNewCommands')
evaluationDependsOn(':wpilibc')
evaluationDependsOn(':wpilibj')
@@ -77,64 +78,19 @@ doxygen.sourceSets.main {
// apriltag
exclude 'apriltag_pose.h'
// Eigen
exclude 'Eigen/**'
exclude 'unsupported/**'
// LLVM
exclude 'wpi/AlignOf.h'
exclude 'wpi/Casting.h'
exclude 'wpi/Chrono.h'
exclude 'wpi/Compiler.h'
exclude 'wpi/ConvertUTF.h'
exclude 'wpi/DenseMap.h'
exclude 'wpi/DenseMapInfo.h'
exclude 'wpi/Endian.h'
exclude 'wpi/EpochTracker.h'
exclude 'wpi/Errc.h'
exclude 'wpi/Errno.h'
exclude 'wpi/ErrorHandling.h'
exclude 'wpi/bit.h'
exclude 'wpi/fs.h'
exclude 'wpi/FunctionExtras.h'
exclude 'wpi/function_ref.h'
exclude 'wpi/iterator.h'
exclude 'wpi/iterator_range.h'
exclude 'wpi/ManagedStatic.h'
exclude 'wpi/MathExtras.h'
exclude 'wpi/MemAlloc.h'
exclude 'wpi/PointerIntPair.h'
exclude 'wpi/PointerLikeTypeTraits.h'
exclude 'wpi/PointerUnion.h'
exclude 'wpi/raw_os_ostream.h'
exclude 'wpi/raw_ostream.h'
exclude 'wpi/SmallPtrSet.h'
exclude 'wpi/SmallSet.h'
exclude 'wpi/SmallString.h'
exclude 'wpi/SmallVector.h'
exclude 'wpi/StringExtras.h'
exclude 'wpi/StringMap.h'
exclude 'wpi/SwapByteOrder.h'
exclude 'wpi/type_traits.h'
exclude 'wpi/VersionTuple.h'
exclude 'wpi/WindowsError.h'
// fmtlib
exclude 'fmt/**'
// libuv
exclude 'uv.h'
exclude 'uv/**'
exclude 'wpinet/uv/**'
// json
exclude 'wpi/adl_serializer.h'
exclude 'wpi/byte_container_with_subtype.h'
exclude 'wpi/detail/**'
exclude 'wpi/json.h'
exclude 'wpi/json_fwd.h'
exclude 'wpi/ordered_map.h'
exclude 'wpi/thirdparty/**'
// mpack
exclude 'wpi/mpack.h'
@@ -218,6 +174,7 @@ task generateJavaDocs(type: Javadoc) {
source project(':epilogue-runtime').sourceSets.main.java
source project(':hal').sourceSets.main.java
source project(':ntcore').sourceSets.main.java
source project(':wpiannotations').sourceSets.main.java
source project(':wpilibNewCommands').sourceSets.main.java
source project(':wpilibj').sourceSets.main.java
source project(':wpimath').sourceSets.main.java

View File

@@ -149,9 +149,14 @@ void glass::DisplayFMSReadOnly(FMSModel* model) {
}
}
if (auto data = model->GetGameSpecificMessageData()) {
wpi::SmallString<64> gsmBuf;
ImGui::Text("Game Specific: %s",
exists ? data->GetValue(gsmBuf).data() : "?");
if (exists) {
wpi::SmallString<64> gsmBuf;
std::string_view gsm = data->GetValue(gsmBuf);
ImGui::Text("Game Specific: %.*s", static_cast<int>(gsm.size()),
gsm.data());
} else {
ImGui::TextUnformatted("Game Specific: ?");
}
}
if (!exists) {

11
javacPlugin/BUILD.bazel Normal file
View File

@@ -0,0 +1,11 @@
load("@rules_java//java:defs.bzl", "java_plugin")
java_plugin(
name = "plugin",
srcs = glob(["src/main/java/**/*.java"]),
resources = glob(["src/main/resources/**"]),
visibility = ["//visibility:public"],
deps = [
"//wpiannotations",
],
)

17
javacPlugin/build.gradle Normal file
View File

@@ -0,0 +1,17 @@
ext {
useJava = true
useCpp = false
baseId = 'wpilibj-javac-plugin'
groupId = 'org.wpilib'
nativeName = ''
devMain = ''
}
apply from: "${rootDir}/shared/java/javacommon.gradle"
dependencies {
implementation project(':wpiannotations')
testImplementation 'com.google.testing.compile:compile-testing:+'
testImplementation project(':wpilibNewCommands')
}

View File

@@ -0,0 +1,224 @@
// 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 com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import org.wpilib.annotation.NoDiscard;
/** Checks for usages of methods that require their return values to be used. */
public class ReturnValueUsedListener implements TaskListener {
private final JavacTask m_task;
private final Set<CompilationUnitTree> m_visitedCUs = new HashSet<>();
public ReturnValueUsedListener(JavacTask task) {
m_task = task;
}
@Override
public void finished(TaskEvent e) {
// We override `finished` instead of `started` because we want to run after the
// ANALYZE attribution phase has completed and assigned types to elements in the AST
// Track the visited CUs to avoid re-processing the same CU multiple times when we call
// `Trees.getElement()` on a tree path.
if (e.getKind() == TaskEvent.Kind.ANALYZE && m_visitedCUs.add(e.getCompilationUnit())) {
e.getCompilationUnit().accept(new Scanner(e.getCompilationUnit()), null);
}
}
private final class Scanner extends TreeScanner<Void, Void> {
private final CompilationUnitTree m_root;
private final Trees m_trees;
Scanner(CompilationUnitTree compilationUnit) {
m_root = compilationUnit;
m_trees = Trees.instance(m_task);
}
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void unused) {
checkIgnoredExpression(node);
return super.visitMethodInvocation(node, unused);
}
@Override
public Void visitNewClass(NewClassTree node, Void unused) {
checkIgnoredExpression(node);
return super.visitNewClass(node, unused);
}
/**
* Common logic for both method invocations and constructor calls when they appear as
* stand-alone expression statements (i.e., their result is ignored).
*/
private void checkIgnoredExpression(Tree node) {
var path = m_trees.getPath(m_root, node);
// Walk the tree upwards to see if the node is directly or indirectly annotated with
// @SuppressWarnings("NoDiscard") or @SuppressWarnings("all"). If so, then we ignore any
// @NoDiscard messages for this node
for (var currentPath = path; currentPath != null; currentPath = currentPath.getParentPath()) {
var element = m_trees.getElement(currentPath);
if (element == null) {
continue;
}
if (element.getAnnotation(SuppressWarnings.class) != null) {
String[] suppressions = element.getAnnotation(SuppressWarnings.class).value();
for (String suppression : suppressions) {
if ("NoDiscard".equals(suppression) || "all".equals(suppression)) {
return;
}
}
}
}
var parentPath = (path == null) ? null : path.getParentPath();
if (parentPath == null || parentPath.getLeaf().getKind() != Tree.Kind.EXPRESSION_STATEMENT) {
// If the parent node is an expression statement, then the value is ignored.
// Otherwise, the value is used and we can ignore this site.
return;
}
// Resolve the static type of the expression
TypeMirror type = getType(node);
if (type == null || type.getKind() == TypeKind.VOID) {
// Skip void (e.g., void-returning methods)
return;
}
// Check @NoDiscard on the invoked executable (method or constructor)
var invoked = getInvokedExecutable(node);
if (invoked != null) {
List<String> messages = getNoDiscardMessages(invoked);
for (String msg : messages) {
m_trees.printMessage(Diagnostic.Kind.ERROR, msg, node, m_root);
}
}
}
private TypeMirror getType(Tree node) {
var path = m_trees.getPath(m_root, node);
if (path == null) {
return null;
}
// Requires running after ANALYZE attribution has completed for this CU.
return m_trees.getTypeMirror(path);
}
private ExecutableElement getInvokedExecutable(Tree node) {
var path = m_trees.getPath(m_root, node);
if (path == null) {
return null;
}
var el = m_trees.getElement(path);
return (el instanceof ExecutableElement ee) ? ee : null;
}
/**
* Collects all @NoDiscard messages applicable to the given executable: - The method/constructor
* itself (if annotated) - The return type (if declared) including its superclasses and all
* implemented interfaces Returns formatted diagnostics messages ready to print.
*/
private List<String> getNoDiscardMessages(ExecutableElement method) {
List<String> messages = new ArrayList<>();
// 1) Method-level @NoDiscard
var methodNoDiscard = method.getAnnotation(NoDiscard.class);
if (methodNoDiscard != null) {
String msg = methodNoDiscard.value();
if (msg.isEmpty()) {
messages.add("Result of @NoDiscard method is ignored");
} else {
messages.add(msg);
}
}
// 2) Type-level @NoDiscard (classes + interfaces recursively)
TypeElement targetType = null;
if (method.getKind() == ElementKind.CONSTRUCTOR) {
// For constructors, the "return type" is the enclosing type
var enclosing = method.getEnclosingElement();
if (enclosing instanceof TypeElement te) {
targetType = te;
}
} else {
var returnType = method.getReturnType();
if (returnType instanceof DeclaredType dt && dt.asElement() instanceof TypeElement te) {
targetType = te;
}
}
if (targetType != null) {
Set<TypeElement> seen = new HashSet<>();
collectNoDiscardMessagesFromTypeHierarchy(targetType, seen, messages);
}
return messages;
}
/**
* Searches for @NoDiscard on the provided type element, its superclasses, and all implemented
* interfaces (recursively). Appends formatted messages to the provided list for every match.
*
* @param type The type element to search
* @param seen A set of type elements that have already been searched
* @param out The list to append messages to
*/
private void collectNoDiscardMessagesFromTypeHierarchy(
TypeElement type, Set<TypeElement> seen, List<String> out) {
if (type == null || !seen.add(type)) {
return;
}
// Check this type directly
var typeNoDiscard = type.getAnnotation(NoDiscard.class);
if (typeNoDiscard != null) {
String message = typeNoDiscard.value();
if (message.isEmpty()) {
out.add(
"Result of method returning @NoDiscard type %s is ignored"
.formatted(type.getQualifiedName()));
} else {
out.add(message);
}
}
// Check superclass chain
var superMirror = type.getSuperclass();
if (superMirror != null && superMirror.getKind() != TypeKind.NONE) {
var superEl = m_task.getTypes().asElement(superMirror);
if (superEl instanceof TypeElement ste) {
collectNoDiscardMessagesFromTypeHierarchy(ste, seen, out);
}
}
// Check all implemented interfaces (recursively)
for (var iface : type.getInterfaces()) {
var ifaceEl = m_task.getTypes().asElement(iface);
if (ifaceEl instanceof TypeElement ite) {
collectNoDiscardMessagesFromTypeHierarchy(ite, seen, out);
}
}
}
}
}

View File

@@ -0,0 +1,31 @@
// 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 com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
/**
* A javac compiler plugin that adds compiler warnings for incorrect usage of WPILib types. Also
* supports WPILib's custom annotations like @NoDiscard.
*/
public class WPILibJavacPlugin implements Plugin {
@Override
public String getName() {
return "WPILib";
}
@Override
public void init(JavacTask task, String... args) {
task.addTaskListener(new ReturnValueUsedListener(task));
}
@Override
public boolean autoStart() {
// autoStart means we don't need to manually pass -Xplugin:WPILib to the javac compiler args
// for the plugin to run
return true;
}
}

View File

@@ -0,0 +1 @@
org.wpilib.javacplugin.WPILibJavacPlugin

View File

@@ -0,0 +1,13 @@
// 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.util.List;
public class CompileTestOptions {
public static final int kJavaVersion = 17;
public static final List<Object> kJavaVersionOptions =
List.of("-source", kJavaVersion, "-target", kJavaVersion);
}

View File

@@ -0,0 +1,638 @@
// 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 static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.Compiler.javac;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.wpilib.javacplugin.CompileTestOptions.kJavaVersionOptions;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import org.junit.jupiter.api.Test;
class ReturnValueUsedListenerTest {
@Test
void nodiscardReturnValueIsUsed() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
class Example {
@NoDiscard
int getI() { return 0; }
void usage() {
int i = getI();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).succeededWithoutWarnings();
}
@Test
void nodiscardReturnValueUnused() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
class Example {
@NoDiscard
int getI() { return 0; }
void usage() {
getI();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(1, compilation.errors().size());
var error = compilation.errors().get(0);
assertEquals("Result of @NoDiscard method is ignored", error.getMessage(null));
}
@Test
void nodiscardOnClass() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
@NoDiscard
class Example {
Example getExample() { return new Example(); }
void usage() {
getExample();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(1, compilation.errors().size());
var error = compilation.errors().get(0);
assertEquals(
"Result of method returning @NoDiscard type frc.robot.Example is ignored",
error.getMessage(null));
}
@Test
void nodiscardOnClassCustomMessage() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
@NoDiscard("Custom message")
class Example {
Example getExample() { return new Example(); }
void usage() {
getExample();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(1, compilation.errors().size());
var error = compilation.errors().get(0);
assertEquals("Custom message", error.getMessage(null));
}
@Test
void nodiscardOnClassAndMethod() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
@NoDiscard
class Example {
@NoDiscard
Example getExample() { return new Example(); }
void usage() {
getExample();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(2, compilation.errors().size());
var error1 = compilation.errors().get(0);
var error2 = compilation.errors().get(1);
assertEquals("Result of @NoDiscard method is ignored", error1.getMessage(null));
assertEquals(
"Result of method returning @NoDiscard type frc.robot.Example is ignored",
error2.getMessage(null));
}
@Test
void nodiscardOnInheritedClass() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
@NoDiscard("Objects of type `Base` must be used")
abstract class Base { }
class Example extends Base {
Example getExample() { return new Example(); }
void usage() {
getExample();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(1, compilation.errors().size());
var error = compilation.errors().get(0);
assertEquals("Objects of type `Base` must be used", error.getMessage(null));
}
@Test
void nodiscardOnSingleInterface() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
@NoDiscard("Objects implementing `I` must be used")
interface I { }
class Example implements I {
Example getExample() { return new Example(); }
void usage() {
getExample();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(1, compilation.errors().size());
var error = compilation.errors().get(0);
assertEquals("Objects implementing `I` must be used", error.getMessage(null));
}
@Test
void nodiscardOnMultipleInterfaces() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
@NoDiscard("Objects implementing `I` must be used")
interface I { }
@NoDiscard("Objects implementing `I2` must be used")
interface I2 { }
class Example implements I, I2 {
Example getExample() { return new Example(); }
void usage() {
getExample();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(2, compilation.errors().size());
var error1 = compilation.errors().get(0);
var error2 = compilation.errors().get(1);
assertEquals("Objects implementing `I` must be used", error1.getMessage(null));
assertEquals("Objects implementing `I2` must be used", error2.getMessage(null));
}
@Test
void nodiscardCustomMessage() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
class Example {
@NoDiscard("Custom message")
int getI() { return 0; }
void usage() {
getI();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(1, compilation.errors().size());
var error = compilation.errors().get(0);
assertEquals("Custom message", error.getMessage(null));
}
@Test
void nodiscardMessageEmptyString() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
class Example {
@NoDiscard("")
int getI() { return 0; }
void usage() {
getI();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(1, compilation.errors().size());
var error = compilation.errors().get(0);
assertEquals("Result of @NoDiscard method is ignored", error.getMessage(null));
}
@Test
void nodiscardOnVoidMethod() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
class Example {
@NoDiscard
void voidMethod() { }
void usage() {
voidMethod();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).succeededWithoutWarnings();
}
@Test
void suppressWarningsOnNoDiscardMethod() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
class Example {
@NoDiscard
Object get() { return null; }
@SuppressWarnings("NoDiscard")
void usage() {
get();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).succeededWithoutWarnings();
}
@Test
void suppressWarningsAllOnNoDiscardMethod() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
class Example {
@NoDiscard
Object get() { return null; }
@SuppressWarnings("all")
void usage() {
get();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).succeededWithoutWarnings();
}
@Test
void suppressWarningsOnNoDiscardClass() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
@SuppressWarnings("NoDiscard")
class Example {
@NoDiscard
Object get() { return null; }
void usage() {
get();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).succeededWithoutWarnings();
}
@Test
void suppressWarningsAllOnNoDiscardClass() {
String source =
"""
package frc.robot;
import org.wpilib.annotation.NoDiscard;
@SuppressWarnings("all")
class Example {
@NoDiscard
Object get() { return null; }
void usage() {
get();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).succeededWithoutWarnings();
}
@Test
void commandsv2CommandFactoryResultIsAssigned() {
String source =
"""
package frc.robot;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
import org.wpilib.annotation.NoDiscard;
class Example {
Command getCommand() {
return Commands.print("");
}
void usage() {
Command theCommand = getCommand();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).succeededWithoutWarnings();
}
@Test
void commandsv2CommandFactoryResultIsPassed() {
String source =
"""
package frc.robot;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
import org.wpilib.annotation.NoDiscard;
class Example {
Command getCommand() {
return Commands.print("");
}
void usage() {
System.out.println(getCommand());
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).succeededWithoutWarnings();
}
@Test
void commandsv2CommandFactoryResultIsChainedAndUsed() {
String source =
"""
package frc.robot;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
import org.wpilib.annotation.NoDiscard;
class Example {
Command getCommand() {
return Commands.print("");
}
void usage() {
Command theCommand = getCommand().withName("The name");
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).succeededWithoutWarnings();
}
@Test
void commandsv2CommandFactoryResultNotUsed() {
String source =
"""
package frc.robot;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
import org.wpilib.annotation.NoDiscard;
class Example {
Command getCommand() {
return Commands.print("");
}
void usage() {
getCommand();
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(1, compilation.errors().size());
var error = compilation.errors().get(0);
assertEquals(
"Commands must be used! Did you mean to bind it to a trigger?", error.getMessage(null));
}
@Test
void commandsv2CommandFactoryResultIsChainedAndNotUsed() {
String source =
"""
package frc.robot;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
import org.wpilib.annotation.NoDiscard;
class Example {
Command getCommand() {
return Commands.print("");
}
void usage() {
getCommand().withName("The name");
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(1, compilation.errors().size());
var error = compilation.errors().get(0);
assertEquals(
"Commands must be used! Did you mean to bind it to a trigger?", error.getMessage(null));
}
@Test
void commandsv2NewCommandInstanceNotUsed() {
String source =
"""
package frc.robot;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
import edu.wpi.first.wpilibj2.command.WaitCommand;
import org.wpilib.annotation.NoDiscard;
class Example {
void usage() {
new WaitCommand(1);
}
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.compile(JavaFileObjects.forSourceString("frc.robot.Example", source));
assertThat(compilation).failed();
assertEquals(1, compilation.errors().size());
var error = compilation.errors().get(0);
assertEquals(
"Commands must be used! Did you mean to bind it to a trigger?", error.getMessage(null));
}
}

View File

@@ -31,6 +31,8 @@ include 'wpilibc'
include 'wpilibcExamples'
include 'wpilibjExamples'
include 'wpilibj'
include 'javacPlugin'
include 'wpiannotations'
include 'wpiunits'
include 'fieldImages'
include 'glass'

View File

@@ -38,12 +38,14 @@ nativeUtils.platformConfigs.each {
}
}
// Compress debug info on Linux
nativeUtils.platformConfigs.each {
if (it.name.contains('linux')) {
// Compress debug info on Linux
it.cppCompiler.debugArgs.add("-gz=zlib")
// Make warning in OpenCV 4.10 from GCC 15 not an error
it.cppCompiler.args.add("-Wno-error=overloaded-virtual")
// Make warning from Google Benchmark not an error
it.cppCompiler.args.add("-Wno-error=restrict")
}
}

View File

@@ -149,7 +149,11 @@ model {
// By default, a development executable will be generated. This is to help the case of
// testing specific functionality of the library.
"${nativeName}Dev"(NativeExecutableSpec) {
targetBuildTypes 'debug'
if (project.hasProperty('ciDebugOnly') || project.hasProperty('debugJNI')) {
targetBuildTypes 'debug'
} else {
targetBuildTypes 'release'
}
sources {
cpp {
source {

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Tyler Veness <calcmogul@gmail.com>
Date: Fri, 8 Sep 2023 19:21:41 -0700
Subject: [PATCH 1/4] Remove version from namespace
Subject: [PATCH 1/5] Remove version from namespace
---
include/nlohmann/detail/abi_macros.hpp | 45 ++------------------------

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Tyler Veness <calcmogul@gmail.com>
Date: Thu, 7 Sep 2023 22:02:27 -0700
Subject: [PATCH 2/4] Make serializer public
Subject: [PATCH 2/5] Make serializer public
---
include/nlohmann/detail/output/serializer.hpp | 4 +++-

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Tyler Veness <calcmogul@gmail.com>
Date: Fri, 8 Sep 2023 21:42:01 -0700
Subject: [PATCH 3/4] Make dump_escaped() take std::string_view
Subject: [PATCH 3/5] Make dump_escaped() take std::string_view
---
include/nlohmann/detail/output/serializer.hpp | 2 +-

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: PJ Reiniger <pj.reiniger@gmail.com>
Date: Wed, 20 Sep 2023 02:23:10 -0400
Subject: [PATCH 4/4] Add llvm stream support
Subject: [PATCH 4/5] Add llvm stream support
---
.../detail/output/output_adapters.hpp | 26 +++++++++++++++++++

View File

@@ -0,0 +1,93 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Gold856 <117957790+Gold856@users.noreply.github.com>
Date: Tue, 27 May 2025 23:39:02 -0400
Subject: [PATCH 5/5] Fix Doxygen warnings
---
include/nlohmann/json.hpp | 31 ++++++-------------------------
1 file changed, 6 insertions(+), 25 deletions(-)
diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp
index a89e2151e589663ba487a462c3d15cd247ff06cf..a5b4f8b4a118c1f5763ec6ba596a8a2d3d5791eb 100644
--- a/include/nlohmann/json.hpp
+++ b/include/nlohmann/json.hpp
@@ -161,7 +161,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using serializer = ::nlohmann::detail::serializer<basic_json>;
using value_t = detail::value_t;
- /// JSON Pointer, see @ref nlohmann::json_pointer
+ /// JSON Pointer, see @ref json_pointer
using json_pointer = ::nlohmann::json_pointer<StringType>;
template<typename T, typename SFINAE>
using json_serializer = JSONSerializer<T, SFINAE>;
@@ -173,7 +173,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
using input_format_t = detail::input_format_t;
- /// SAX interface type, see @ref nlohmann::json_sax
+ /// SAX interface type, see nlohmann::json_sax
using json_sax_t = json_sax<basic_json>;
////////////////
@@ -1606,13 +1606,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@throw what @ref json_serializer<ValueType> `from_json()` method throws
- @liveexample{The example below shows several conversions from JSON values
- to other types. There a few things to note: (1) Floating-point numbers can
- be converted to integers\, (2) A JSON array can be converted to a standard
- `std::vector<short>`\, (3) A JSON object can be converted to C++
- associative containers such as `std::unordered_map<std::string\,
- json>`.,get__ValueType_const}
-
@since version 2.1.0
*/
template < typename ValueType,
@@ -1678,7 +1671,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@return a copy of *this, converted into @a BasicJsonType
- @complexity Depending on the implementation of the called `from_json()`
+ Complexity: Depending on the implementation of the called `from_json()`
method.
@since version 3.2.0
@@ -1702,7 +1695,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@return a copy of *this
- @complexity Constant.
+ Complexity: Constant.
@since version 2.1.0
*/
@@ -1786,12 +1779,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@return pointer to the internally stored JSON value if the requested
pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
- @complexity Constant.
-
- @liveexample{The example below shows how pointers to internal values of a
- JSON value can be requested. Note that no type conversions are made and a
- `nullptr` is returned if the value and the requested pointer type does not
- match.,get__PointerType}
+ Complexity: Constant.
@sa see @ref get_ptr() for explicit pointer-member access
@@ -1883,14 +1871,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
to the JSON value type (e.g., the JSON value is of type boolean, but a
string is requested); see example below
- @complexity Linear in the size of the JSON value.
-
- @liveexample{The example below shows several conversions from JSON values
- to other types. There a few things to note: (1) Floating-point numbers can
- be converted to integers\, (2) A JSON array can be converted to a standard
- `std::vector<short>`\, (3) A JSON object can be converted to C++
- associative containers such as `std::unordered_map<std::string\,
- json>`.,operator__ValueType}
+ Complexity: Linear in the size of the JSON value.
@since version 1.0.0
*/

View File

@@ -0,0 +1,8 @@
load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "wpiannotations",
srcs = glob(["src/main/java/**/*.java"]),
visibility = ["//visibility:public"],
deps = [],
)

View File

@@ -0,0 +1,37 @@
project(wpiannotations)
# Java bindings
if(WITH_JAVA)
include(UseJava)
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
add_jar(
wpiannotations_jar
${JAVA_SOURCES}
OUTPUT_NAME wpiannotations
OUTPUT_DIR ${WPILIB_BINARY_DIR}/${java_lib_dest}
)
set_property(TARGET wpiannotations_jar PROPERTY FOLDER "java")
install_jar(wpiannotations_jar DESTINATION ${java_lib_dest})
install_jar_exports(
TARGETS wpiannotations_jar
FILE wpiannotations.cmake
DESTINATION share/wpiannotations
)
install(FILES wpiannotations-config.cmake DESTINATION share/wpiannotations)
endif()
if(WITH_JAVA_SOURCE)
include(UseJava)
include(CreateSourceJar)
add_source_jar(
wpiannotations_src_jar
BASE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/src/main/java
OUTPUT_NAME wpiannotations-sources
)
set_property(TARGET wpiannotations_src_jar PROPERTY FOLDER "java")
install_jar(wpiannotations_src_jar DESTINATION ${java_lib_dest})
endif()

View File

@@ -0,0 +1,11 @@
ext {
useJava = true
useCpp = false
baseId = 'annotations'
groupId = 'org.wpilib'
nativeName = ''
devMain = ''
}
apply from: "${rootDir}/shared/java/javacommon.gradle"

View File

@@ -0,0 +1,27 @@
// 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a method as returning a value that must be used. The WPILib compiler plugin will check for
* uses of methods with this annotation and report a compiler error if the value is unused. Marking
* a class or interface as {@code @NoDiscard} will act as if any method that returns that type or
* any subclass or implementor of that type has been marked with {@code @NoDiscard}.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.CLASS) // needs to be stored in the class for use by libraries
public @interface NoDiscard {
/**
* An error message to display if the return value is not used.
*
* @return The error message.
*/
String value() default "";
}

View File

@@ -0,0 +1,2 @@
get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
include(${SELF_DIR}/wpiannotations.cmake)

View File

@@ -106,11 +106,14 @@ wpilib_java_library(
srcs = glob(["src/main/java/**/*.java"]) + [":generated_java"],
maven_artifact_name = "wpilibNewCommands-java",
maven_group_id = "edu.wpi.first.wpilibNewCommands",
exported_plugins = ["//javacPlugin:plugin"],
plugins = ["//javacPlugin:plugin"],
visibility = ["//visibility:public"],
deps = [
"//cscore:cscore-java",
"//hal:hal-java",
"//ntcore:ntcore-java",
"//wpiannotations",
"//wpilibj:wpilibj-java",
"//wpimath:wpimath-java",
"//wpinet:wpinet-java",
@@ -165,8 +168,10 @@ java_binary(
srcs = ["src/dev/java/edu/wpi/first/wpilibj2/commands/DevMain.java"],
main_class = "edu.wpi.first.wpilibj2.commands.DevMain",
deps = [
":wpilibNewCommands-java",
"//hal:hal-java",
"//ntcore:ntcore-java",
"//wpiannotations",
"//wpimath:wpimath-java",
"//wpiutil:wpiutil-java",
],

View File

@@ -23,6 +23,7 @@ if(WITH_JAVA)
wpiutil_jar
wpilibj_jar
datalog_jar
wpiannotations_jar
OUTPUT_NAME wpilibNewCommands
OUTPUT_DIR ${WPILIB_BINARY_DIR}/${java_lib_dest}
)

View File

@@ -21,8 +21,10 @@ dependencies {
implementation project(':hal')
implementation project(':wpimath')
implementation project(':wpilibj')
implementation project(':wpiannotations')
api project(':datalog')
testImplementation 'org.mockito:mockito-core:4.1.0'
annotationProcessor project(':javacPlugin')
}
sourceSets.main.java.srcDir "${projectDir}/src/generated/main/java"

View File

@@ -16,6 +16,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BooleanSupplier;
import org.wpilib.annotation.NoDiscard;
/**
* A state machine representing a complete action to be performed by the robot. Commands are run by
@@ -27,6 +28,7 @@ import java.util.function.BooleanSupplier;
*
* <p>This class is provided by the NewCommands VendorDep
*/
@NoDiscard("Commands must be used! Did you mean to bind it to a trigger?")
public abstract class Command implements Sendable {
/** Requirements set. */
private final Set<Subsystem> m_requirements = new HashSet<>();

View File

@@ -2,7 +2,7 @@
// 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.
#include "../../include/frc/controller/BangBangController.h"
#include "frc/controller/BangBangController.h"
#include <wpi/sendable/SendableBuilder.h>

View File

@@ -1992,6 +1992,7 @@ namespace units
* @param[in] rhs unit to copy.
*/
template<class UnitsRhs, typename Ty, template<typename> class NlsRhs>
requires traits::is_unit_v<UnitsRhs> && traits::is_unit_v<Units> && traits::is_convertible_unit_v<UnitsRhs, Units>
constexpr unit_t(const unit_t<UnitsRhs, Ty, NlsRhs>& rhs) noexcept :
nls(units::convert<UnitsRhs, Units, T>(rhs.m_value), std::true_type() /*store linear value*/)
{

View File

@@ -3513,3 +3513,18 @@ TEST_F(CaseStudies, pythagoreanTheorum) {
pow<2>(RightTriangle::b::value()) ==
pow<2>(RightTriangle::c::value()));
}
TEST(Units, overloadResolution) {
// Slight hack to get nested functions
struct Scope {
static bool f(units::meter_t) {
return true;
};
static bool f(units::second_t) {
return false;
};
};
// Make sure this properly selects the meter overload
EXPECT_TRUE(Scope::f(1_mm));
}

View File

@@ -49,7 +49,7 @@ class FsEvent final : public HandleImpl<FsEvent, uv_fs_event_t> {
* Start watching the specified path for changes.
*
* @param path Path to watch for changes
* @param events Bitmask of event flags. Only UV_FS_EVENT_RECURSIVE is
* @param flags Bitmask of event flags. Only UV_FS_EVENT_RECURSIVE is
* supported (and only on OSX and Windows).
*/
void Start(std::string_view path, unsigned int flags = 0);

View File

@@ -87,7 +87,6 @@ void GetNameInfo(Loop& loop,
* @param callback Callback function to call when resolution completes
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional flags to modify the behavior of `getnameinfo`.
* @return Connection object for the callback
*/
inline void GetNameInfo(const std::shared_ptr<Loop>& loop,
std::function<void(const char*, const char*)> callback,

View File

@@ -90,7 +90,6 @@ class Stream : public Handle {
* complete. Errors will be reported to the stream error handler.
*
* @param callback Callback function to call when shutdown completes
* @return Connection object for the callback
*/
void Shutdown(std::function<void()> callback = nullptr);

View File

@@ -146,7 +146,6 @@ class Udp final : public HandleImpl<Udp, uv_udp_t> {
*
* @param ip The address to which to bind.
* @param port The port to which to bind.
* @param flags Optional additional flags.
*/
void Connect6(std::string_view ip, unsigned int port);

View File

@@ -649,17 +649,10 @@ public interface Measure<U extends Unit> extends Comparable<Measure<U>> {
return unit().ofBaseUnits(baseUnitResult);
}
if (unit() instanceof DimensionlessUnit) {
// Numerator is a dimensionless
if (divisor.unit() instanceof PerUnit<?, ?> ratio) {
// Dividing by a ratio, return its reciprocal scaled by this
return ratio.reciprocal().ofBaseUnits(baseUnitResult);
}
if (divisor.unit() instanceof PerUnit<?, ?> ratio) {
// Dividing by a Per<Time, U>, return its reciprocal velocity scaled by this
// Note: Per<Time, U>.reciprocal() is coded to return a Velocity<U>
return ratio.reciprocal().ofBaseUnits(baseUnitResult);
}
// Numerator is a dimensionless
if (unit() instanceof DimensionlessUnit && divisor.unit() instanceof PerUnit<?, ?> ratio) {
// Dividing by a ratio, return its reciprocal scaled by this
return ratio.reciprocal().ofBaseUnits(baseUnitResult);
}
if (divisor.unit() instanceof PerUnit<?, ?> ratio

View File

@@ -10,6 +10,7 @@ import edu.wpi.first.util.WPISerializable;
* Marker interface to indicate a class is serializable using Struct serialization.
*
* <p>While this cannot be enforced by the interface, any class implementing this interface should
* provide a public final static `struct` member variable.
* provide a public final static `struct` member variable, or a static final `getStruct()` method if
* the class is generic.
*/
public interface StructSerializable extends WPISerializable {}

View File

@@ -33,7 +33,7 @@ class circular_buffer {
class iterator {
public:
using iterator_category = std::forward_iterator_tag;
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
@@ -51,6 +51,15 @@ class circular_buffer {
++(*this);
return retval;
}
constexpr iterator& operator--() {
--m_index;
return *this;
}
constexpr iterator operator--(int) {
iterator retval = *this;
--(*this);
return retval;
}
constexpr bool operator==(const iterator&) const = default;
constexpr reference operator*() { return (*m_buffer)[m_index]; }
@@ -61,7 +70,7 @@ class circular_buffer {
class const_iterator {
public:
using iterator_category = std::forward_iterator_tag;
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
@@ -79,6 +88,15 @@ class circular_buffer {
++(*this);
return retval;
}
constexpr const_iterator& operator--() {
--m_index;
return *this;
}
constexpr const_iterator operator--(int) {
const_iterator retval = *this;
--(*this);
return retval;
}
constexpr bool operator==(const const_iterator&) const = default;
constexpr const_reference operator*() const { return (*m_buffer)[m_index]; }
@@ -87,21 +105,83 @@ class circular_buffer {
size_t m_index;
};
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/**
* Returns begin iterator.
*/
constexpr iterator begin() { return iterator(this, 0); }
/**
* Returns end iterator.
*/
constexpr iterator end() {
return iterator(this, ::wpi::circular_buffer<T>::size());
}
/**
* Returns const begin iterator.
*/
constexpr const_iterator begin() const { return const_iterator(this, 0); }
/**
* Returns const end iterator.
*/
constexpr const_iterator end() const {
return const_iterator(this, ::wpi::circular_buffer<T>::size());
}
/**
* Returns const begin iterator.
*/
constexpr const_iterator cbegin() const { return const_iterator(this, 0); }
/**
* Returns const end iterator.
*/
constexpr const_iterator cend() const {
return const_iterator(this, ::wpi::circular_buffer<T>::size());
}
/**
* Returns reverse begin iterator.
*/
constexpr reverse_iterator rbegin() { return reverse_iterator(end()); }
/**
* Returns reverse end iterator.
*/
constexpr reverse_iterator rend() { return reverse_iterator(begin()); }
/**
* Returns const reverse begin iterator.
*/
constexpr const_reverse_iterator rbegin() const {
return const_reverse_iterator(end());
}
/**
* Returns const reverse end iterator.
*/
constexpr const_reverse_iterator rend() const {
return const_reverse_iterator(begin());
}
/**
* Returns const reverse begin iterator.
*/
constexpr const_reverse_iterator crbegin() const {
return const_reverse_iterator(cend());
}
/**
* Returns const reverse end iterator.
*/
constexpr const_reverse_iterator crend() const {
return const_reverse_iterator(cbegin());
}
/**
* Returns number of elements in buffer
*/

View File

@@ -24,7 +24,7 @@ class static_circular_buffer {
class iterator {
public:
using iterator_category = std::forward_iterator_tag;
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
@@ -42,6 +42,15 @@ class static_circular_buffer {
++(*this);
return retval;
}
constexpr iterator& operator--() {
--m_index;
return *this;
}
constexpr iterator operator--(int) {
iterator retval = *this;
--(*this);
return retval;
}
constexpr bool operator==(const iterator&) const = default;
constexpr reference operator*() { return (*m_buffer)[m_index]; }
@@ -52,7 +61,7 @@ class static_circular_buffer {
class const_iterator {
public:
using iterator_category = std::forward_iterator_tag;
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
@@ -70,6 +79,15 @@ class static_circular_buffer {
++(*this);
return retval;
}
constexpr const_iterator& operator--() {
--m_index;
return *this;
}
constexpr const_iterator operator--(int) {
const_iterator retval = *this;
--(*this);
return retval;
}
constexpr bool operator==(const const_iterator&) const = default;
constexpr const_reference operator*() const { return (*m_buffer)[m_index]; }
@@ -78,6 +96,9 @@ class static_circular_buffer {
size_t m_index;
};
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/**
* Returns begin iterator.
*/
@@ -91,29 +112,67 @@ class static_circular_buffer {
}
/**
* Returns begin iterator.
* Returns const begin iterator.
*/
constexpr const_iterator begin() const { return const_iterator(this, 0); }
/**
* Returns end iterator.
* Returns const end iterator.
*/
constexpr const_iterator end() const {
return const_iterator(this, ::wpi::static_circular_buffer<T, N>::size());
}
/**
* Returns begin iterator.
* Returns const begin iterator.
*/
constexpr const_iterator cbegin() const { return const_iterator(this, 0); }
/**
* Returns end iterator.
* Returns const end iterator.
*/
constexpr const_iterator cend() const {
return const_iterator(this, ::wpi::static_circular_buffer<T, N>::size());
}
/**
* Returns reverse begin iterator.
*/
constexpr reverse_iterator rbegin() { return reverse_iterator(end()); }
/**
* Returns reverse end iterator.
*/
constexpr reverse_iterator rend() { return reverse_iterator(begin()); }
/**
* Returns const reverse begin iterator.
*/
constexpr const_reverse_iterator rbegin() const {
return const_reverse_iterator(end());
}
/**
* Returns const reverse end iterator.
*/
constexpr const_reverse_iterator rend() const {
return const_reverse_iterator(begin());
}
/**
* Returns const reverse begin iterator.
*/
constexpr const_reverse_iterator crbegin() const {
return const_reverse_iterator(cend());
}
/**
* Returns const reverse end iterator.
*/
constexpr const_reverse_iterator crend() const {
return const_reverse_iterator(cbegin());
}
/**
* Returns number of elements in buffer
*/

View File

@@ -161,7 +161,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using serializer = ::wpi::detail::serializer<basic_json>;
using value_t = detail::value_t;
/// JSON Pointer, see @ref wpi::json_pointer
/// JSON Pointer, see @ref json_pointer
using json_pointer = ::wpi::json_pointer<StringType>;
template<typename T, typename SFINAE>
using json_serializer = JSONSerializer<T, SFINAE>;
@@ -173,7 +173,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
using input_format_t = detail::input_format_t;
/// SAX interface type, see @ref wpi::json_sax
/// SAX interface type, see wpi::json_sax
using json_sax_t = json_sax<basic_json>;
////////////////
@@ -1606,13 +1606,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@throw what @ref json_serializer<ValueType> `from_json()` method throws
@liveexample{The example below shows several conversions from JSON values
to other types. There a few things to note: (1) Floating-point numbers can
be converted to integers\, (2) A JSON array can be converted to a standard
`std::vector<short>`\, (3) A JSON object can be converted to C++
associative containers such as `std::unordered_map<std::string\,
json>`.,get__ValueType_const}
@since version 2.1.0
*/
template < typename ValueType,
@@ -1678,7 +1671,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@return a copy of *this, converted into @a BasicJsonType
@complexity Depending on the implementation of the called `from_json()`
Complexity: Depending on the implementation of the called `from_json()`
method.
@since version 3.2.0
@@ -1702,7 +1695,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@return a copy of *this
@complexity Constant.
Complexity: Constant.
@since version 2.1.0
*/
@@ -1786,12 +1779,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@return pointer to the internally stored JSON value if the requested
pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
@complexity Constant.
@liveexample{The example below shows how pointers to internal values of a
JSON value can be requested. Note that no type conversions are made and a
`nullptr` is returned if the value and the requested pointer type does not
match.,get__PointerType}
Complexity: Constant.
@sa see @ref get_ptr() for explicit pointer-member access
@@ -1883,14 +1871,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
to the JSON value type (e.g., the JSON value is of type boolean, but a
string is requested); see example below
@complexity Linear in the size of the JSON value.
@liveexample{The example below shows several conversions from JSON values
to other types. There a few things to note: (1) Floating-point numbers can
be converted to integers\, (2) A JSON array can be converted to a standard
`std::vector<short>`\, (3) A JSON object can be converted to C++
associative containers such as `std::unordered_map<std::string\,
json>`.,operator__ValueType}
Complexity: Linear in the size of the JSON value.
@since version 1.0.0
*/

View File

@@ -251,4 +251,18 @@ TEST(CircularBufferTest, Iterator) {
EXPECT_EQ(values[i], elem);
++i;
}
// reverse_iterator
i = 2;
for (auto it = queue.rbegin(); it != queue.rend(); ++it) {
EXPECT_EQ(values[i], *it);
--i;
}
// const_reverse_iterator
i = 2;
for (auto it = queue.crbegin(); it != queue.crend(); ++it) {
EXPECT_EQ(values[i], *it);
--i;
}
}

View File

@@ -145,4 +145,18 @@ TEST(StaticCircularBufferTest, Iterator) {
EXPECT_EQ(values[i], elem);
++i;
}
// reverse_iterator
i = 2;
for (auto it = queue.rbegin(); it != queue.rend(); ++it) {
EXPECT_EQ(values[i], *it);
--i;
}
// const_reverse_iterator
i = 2;
for (auto it = queue.crbegin(); it != queue.crend(); ++it) {
EXPECT_EQ(values[i], *it);
--i;
}
}