Enable JSpecify support in NullAway

Except in the common main classes: there's a couple of bugs in the
current version which crash the type checker.
This commit is contained in:
Jonathan Coates
2026-10-19 20:15:53 +01:00
parent be75b7357f
commit 4cd6cdb69c
63 changed files with 201 additions and 110 deletions
@@ -89,6 +89,7 @@ tasks.withType(JavaCompile::class.java).configureEach {
option("UnusedMethod:ExemptingMethodAnnotations", "dan200.computercraft.api.lua.LuaFunction")
check("NullAway", CheckSeverity.ERROR)
check("JSpecifyUnrecognizedAnnotationLocation", CheckSeverity.ERROR)
option(
"NullAway:AnnotatedPackages",
listOf("dan200.computercraft", "cc.tweaked", "net.fabricmc.fabric.api").joinToString(","),
@@ -97,6 +98,8 @@ tasks.withType(JavaCompile::class.java).configureEach {
option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull")
option("NullAway:CheckOptionalEmptiness")
option("NullAway:AcknowledgeRestrictiveAnnotations")
option("NullAway:JSpecifyMode")
option("NullAway:JSpecifyExperimental")
excludedPaths.set(".*/jmh_generated/.*")
}
@@ -54,6 +54,10 @@ val verifyClassesMatch = tasks.register<VerifyJavaCompile>("verifyClassesMatch")
check("MissingOverride", CheckSeverity.OFF)
check("LoaderOverride", CheckSeverity.DEFAULT)
check("MissingLoaderOverride", CheckSeverity.DEFAULT)
// TODO: Enable JSpecify in the common classes. Unfortunately there's several bugs blocking us right now.
option("NullAway:JSpecifyMode", false)
option("NullAway:JSpecifyExperimental", false)
}
source = minecraftEmbeddedProjectResolved.map {
+1 -1
View File
@@ -70,7 +70,7 @@ illuaminate = "0.1.0-83-g1131f68"
lwjgl = "3.4.3"
minotaur = "2.9.0"
modDevGradle = "2.0.147"
nullAway = "0.14.0"
nullAway = "0.14.2"
shadow = "9.6.1"
spotless = "8.10.4"
teavm = "0.16.0"
@@ -15,7 +15,6 @@ import java.util.stream.Stream;
*
* @since 1.115
*/
@Nullable
public interface PrintoutContents {
/**
* Get the (possibly empty) title for this printout.
+9
View File
@@ -8,6 +8,7 @@ import cc.tweaked.gradle.MergeTrees
import cc.tweaked.gradle.annotationProcessorEverywhere
import cc.tweaked.vanillaextract.configurations.Capabilities.clientClasses
import cc.tweaked.vanillaextract.configurations.Capabilities.commonClasses
import net.ltgt.gradle.errorprone.errorprone
plugins {
`java-test-fixtures`
@@ -70,6 +71,14 @@ dependencies {
"cctJavadoc"(libs.cctJavadoc)
}
// TODO: Enable JSpecify in the common classes. Unfortunately there's several bugs blocking us right now.
tasks.compileJava {
options.errorprone {
option("NullAway:JSpecifyMode", false)
option("NullAway:JSpecifyExperimental", false)
}
}
illuaminate {
version = libs.versions.illuaminate
}
@@ -190,7 +190,7 @@ public final class ClientRegistry {
return layer == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF;
}
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<@Nullable ShaderInstance>> load) throws IOException {
RenderTypes.registerShaders(resources, (name, create, onLoaded) -> {
ShaderInstance shader;
try {
@@ -183,7 +183,8 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
return;
}
var name = file.getFileName().toString();
var fileName = file.getFileName();
var name = fileName == null ? "unknown" : fileName.toString();
if (name.length() > UploadFileMessage.MAX_FILE_NAME) {
alert(UploadResult.FAILED_TITLE, Component.translatable("gui.computercraft.upload.failed.name_too_long"));
return;
@@ -7,6 +7,7 @@ package dan200.computercraft.client.integration;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
import dan200.computercraft.client.render.vbo.DirectVertexBuffer;
import dan200.computercraft.core.util.Nullability;
import java.nio.ByteBuffer;
import java.util.Optional;
@@ -48,10 +49,10 @@ public class ShaderMod {
}
private static final class Storage {
static final ShaderMod INSTANCE = ServiceLoader.load(Provider.class)
static final ShaderMod INSTANCE = Nullability.assertNonNull(ServiceLoader.load(Provider.class)
.stream()
.flatMap(x -> x.get().get().stream())
.findFirst()
.orElseGet(ShaderMod::new);
.orElseGet(ShaderMod::new));
}
}
@@ -7,6 +7,7 @@ package dan200.computercraft.data;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.util.PrettyJsonWriter;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
@@ -38,7 +39,7 @@ public record PrettyDataProvider<T extends DataProvider>(T provider) implements
@Override
public void writeIfNeeded(Path path, byte[] bytes, HashCode hashCode) throws IOException {
var name = path.getFileName().toString();
var name = Nullability.assertNonNull(path.getFileName()).toString();
if (name.endsWith(".json") || name.endsWith(".mcmeta")) {
bytes = PrettyJsonWriter.reformat(bytes);
hashCode = HASH_FUNCTION.hashBytes(bytes);
@@ -5,9 +5,9 @@
package dan200.computercraft.impl.network.wired;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.core.util.Nullability;
import org.jspecify.annotations.Nullable;
import java.util.Objects;
/**
* A disjoint-set/union-find of {@link WiredNodeImpl}s.
@@ -95,6 +95,6 @@ class NodeSet {
* @return The associated network.
*/
WiredNetworkImpl network() {
return Objects.requireNonNull(find().network);
return Nullability.assertNonNull(find().network);
}
}
@@ -17,6 +17,7 @@ import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
@@ -123,7 +124,6 @@ import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Predicate;
@@ -480,7 +480,7 @@ public final class ModRegistry {
ComputerCraftAPI.registerAPIFactory(computer -> {
var turtle = computer.getComponent(ComputerComponents.TURTLE);
var metrics = Objects.requireNonNull(computer.getComponent(ServerComputer.METRICS));
var metrics = Nullability.assertNonNull(computer.getComponent(ServerComputer.METRICS));
return turtle == null ? null : new TurtleAPI(metrics, (TurtleAccessInternal) turtle);
});
@@ -60,6 +60,6 @@ class ItemDetailsTest {
@SuppressWarnings("unchecked")
private static Matcher<Map<? extends String, ?>> containsEntryWith(String key, Matcher<?> value) {
return CustomMatchers.containsEntryWith(key, (Matcher<? super Object>) value);
return CustomMatchers.containsEntryWith(key, (Matcher<Object>) value);
}
}
@@ -9,6 +9,7 @@ import com.mojang.blaze3d.platform.Lighting;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.VertexSorting;
import dan200.computercraft.core.util.Nullability;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.FogRenderer;
import org.joml.Matrix4f;
@@ -33,7 +34,7 @@ public class ImageRenderer implements AutoCloseable {
}
public void captureRender(Path output, Runnable render) throws IOException {
Files.createDirectories(output.getParent());
Files.createDirectories(Nullability.assertNonNull(output.getParent()));
framebuffer.setClearColor(0, 0, 0, 0);
framebuffer.clear(Minecraft.ON_OSX);
@@ -4,6 +4,7 @@
package dan200.computercraft.mixin.gametest;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.gametest.core.TestHooks;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
@@ -12,8 +13,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Objects;
/**
* Patches {@link NbtUtils#structureToSnbt(CompoundTag)} to remove air blocks from the structure file. This
* significantly reduces the size of our generated templates.
@@ -27,7 +26,7 @@ abstract class NbtUtilsMixin {
@SuppressWarnings("unused")
private static void structureToSnbt(CompoundTag tag, CallbackInfoReturnable<String> ci) {
// Load in the structure, strip out air, then save it back again.
var structure = Objects.requireNonNull(TestHooks.getStructureManager()).readStructure(tag);
var structure = Nullability.assertNonNull(TestHooks.getStructureManager()).readStructure(tag);
var palette = ((StructureTemplateAccessor) structure).getPalettes().get(0);
palette.blocks().removeIf(x -> x.state().isAir());
var newTag = structure.save(new CompoundTag());
@@ -74,7 +74,7 @@ public interface IArguments {
* @see #get(int) To get a single argument.
*/
default @Nullable Object[] getAll() throws LuaException {
var result = new Object[count()];
var result = new @Nullable Object[count()];
for (var i = 0; i < result.length; i++) result[i] = get(i);
return result;
}
@@ -458,7 +458,7 @@ public interface LuaTable<K, V> extends Map<K, V> {
}
@Override
default V remove(Object o) {
default V remove(@Nullable Object o) {
throw new UnsupportedOperationException("Cannot modify LuaTable");
}
@@ -8,6 +8,7 @@ import org.jspecify.annotations.Nullable;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Objects;
/**
* Various utility functions for operating with Lua values.
@@ -159,7 +160,7 @@ public final class LuaValues {
* @throws LuaException If this is not a known enum value.
*/
public static <T extends Enum<T>> T checkEnum(int index, Class<T> klass, String value) throws LuaException {
for (var possibility : klass.getEnumConstants()) {
for (var possibility : Objects.requireNonNull(klass.getEnumConstants())) {
if (possibility.name().equalsIgnoreCase(value)) return possibility;
}
@@ -57,7 +57,7 @@ public final class ObjectArguments implements IArguments {
}
@Override
public Object[] getAll() {
public @Nullable Object[] getAll() {
return args.toArray();
}
}
@@ -17,6 +17,7 @@ import java.util.Set;
public class ObjectLuaTable implements LuaTable<Object, Object> {
private final Map<Object, Object> map;
@SuppressWarnings("NullAway")
public ObjectLuaTable(Map<?, ?> map) {
this.map = Collections.unmodifiableMap(map);
}
@@ -32,18 +33,17 @@ public class ObjectLuaTable implements LuaTable<Object, Object> {
}
@Override
public boolean containsKey(Object o) {
public boolean containsKey(@Nullable Object o) {
return map.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
public boolean containsValue(@Nullable Object o) {
return map.containsKey(o);
}
@Nullable
@Override
public Object get(Object o) {
public @Nullable Object get(@Nullable Object o) {
return map.get(o);
}
@@ -63,7 +63,7 @@ public class ObjectLuaTable implements LuaTable<Object, Object> {
}
@Override
public boolean equals(Object o) {
public boolean equals(@Nullable Object o) {
return this == o || o instanceof Map<?, ?> otherMap && map.equals(otherMap);
}
@@ -8,6 +8,7 @@ import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.core.util.StringUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -135,7 +136,7 @@ public class OSAPI implements ILuaAPI {
* @param args The parameters of the event.
* @cc.tparam string name The name of the event to queue.
* @cc.param ... The parameters of the event. These can be any primitive type (boolean, number, string) as well as
* tables. Other types (like functions), as well as metatables, will not be preserved.
* tables. Other types (like functions), as well as metatables, will not be preserved.
* @cc.see os.pullEvent To pull the event queued
*/
@LuaFunction
@@ -408,7 +409,6 @@ public class OSAPI implements ILuaAPI {
* <pre>{@code
* os.date("%A %d %B %Y") -- See the reference above!
* }</pre>
*
* @cc.usage Convert a timestamp to a table.
* <pre>{@code
* os.date("!*t", 1242534247)
@@ -430,7 +430,7 @@ public class OSAPI implements ILuaAPI {
@LuaFunction
public final Object date(Optional<String> formatA, Optional<Long> timeA) throws LuaException {
var format = formatA.orElse("%c");
long time = timeA.orElseGet(() -> Instant.now().getEpochSecond());
long time = Nullability.assertNonNull(timeA.orElseGet(() -> Instant.now().getEpochSecond()));
var instant = Instant.ofEpochSecond(time);
ZonedDateTime date;
@@ -187,7 +187,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
private final IAPIEnvironment environment;
private final MethodSupplier<PeripheralMethod> peripheralMethods;
private final PeripheralWrapper[] peripherals = new PeripheralWrapper[6];
private final @Nullable PeripheralWrapper[] peripherals = new PeripheralWrapper[6];
private boolean running;
public PeripheralAPI(IAPIEnvironment environment, MethodSupplier<PeripheralMethod> peripheralMethods) {
@@ -71,6 +71,8 @@ public final class AddressRule {
var port = socketAddress.getPort();
var address = socketAddress.getAddress();
if (address == null) throw new IllegalStateException("Socket address must be resolved");
var ipv4Address = address instanceof Inet6Address inet6 && InetAddresses.is6to4Address(inet6)
? InetAddresses.get6to4IPv4Address(inet6) : null;
@@ -11,6 +11,7 @@ import com.google.common.primitives.Primitives;
import com.google.common.reflect.TypeToken;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.methods.LuaMethod;
import dan200.computercraft.core.util.Nullability;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -179,7 +180,7 @@ final class Generator<T> {
}
// unsafe can only be used on the computer thread, so reject it for mainThread functions.
var annotation = method.getAnnotation(LuaFunction.class);
var annotation = Nullability.assertNonNull(method.getAnnotation(LuaFunction.class));
if (annotation.unsafe() && annotation.mainThread()) {
LOG.error("Lua Method {}.{} cannot use unsafe and mainThread.", method.getDeclaringClass().getName(), method.getName());
return false;
@@ -231,7 +232,7 @@ final class Generator<T> {
private Optional<T> build(Method method, MethodHandle handle, List<Type> parameters) {
LOG.debug("Generating method wrapper for {}.{}.", method.getDeclaringClass().getName(), method.getName());
var annotation = method.getAnnotation(LuaFunction.class);
var annotation = Nullability.assertNonNull(method.getAnnotation(LuaFunction.class));
var wrappedHandle = buildMethodHandle(method, handle, parameters, annotation.unsafe());
if (wrappedHandle == null) return Optional.empty();
@@ -4,12 +4,14 @@
package dan200.computercraft.core.asm;
import org.jspecify.annotations.Nullable;
import java.util.Arrays;
import java.util.function.IntFunction;
final class IntCache<T> {
private final IntFunction<T> factory;
private volatile Object[] cache = new Object[16];
private volatile @Nullable Object[] cache = new Object[16];
IntCache(IntFunction<T> factory) {
this.factory = factory;
@@ -30,7 +30,7 @@ final class MethodSupplierImpl<T> implements MethodSupplier<T> {
private final List<GenericMethod> genericMethods;
private final Generator<T> generator;
private final IntCache<T> dynamic;
private final Function<Object, String[]> dynamicMethods;
private final Function<Object, String @Nullable []> dynamicMethods;
private final ClassValue<List<NamedMethod<T>>> classCache = new ClassValue<>() {
private final Function<Class<?>, List<NamedMethod<T>>> getter = catching(MethodSupplierImpl.this::getMethodsImpl, List.of());
@@ -45,7 +45,7 @@ final class MethodSupplierImpl<T> implements MethodSupplier<T> {
List<GenericMethod> genericMethods,
Generator<T> generator,
IntCache<T> dynamic,
Function<Object, String[]> dynamicMethods
Function<Object, String @Nullable []> dynamicMethods
) {
this.genericMethods = genericMethods;
this.generator = generator;
@@ -31,7 +31,7 @@ public final class Environment implements IAPIEnvironment {
private final ComputerEnvironment environment;
private final MetricsObserver metrics;
private final IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT];
private final @Nullable IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT];
private @Nullable IPeripheralChangeListener peripheralListener = null;
private final Int2ObjectMap<Timer> timers = new Int2ObjectOpenHashMap<>();
@@ -120,7 +120,7 @@ public final class ComputerThread implements ComputerScheduler {
* The array of current workers, and their owning threads.
*/
@GuardedBy("threadLock")
private final WorkerThread[] workers;
private final @Nullable WorkerThread[] workers;
/**
* The number of workers in {@link #workers}.
@@ -187,7 +187,7 @@ public final class ComputerThread implements ComputerScheduler {
}
@SuppressWarnings("GuardedBy")
private WorkerThread[] workersReadOnly() {
private @Nullable WorkerThread[] workersReadOnly() {
return workers;
}
@@ -204,7 +204,9 @@ public final class ComputerThread implements ComputerScheduler {
try {
LOG.trace("Possibly spawning a worker or monitor.");
if (monitor == null || !monitor.isAlive()) (monitor = monitorFactory.newThread(new Monitor())).start();
if (monitor == null || !monitor.isAlive()) {
(monitor = Objects.requireNonNull(monitorFactory.newThread(new Monitor()), "Cannot create monitor thread")).start();
}
if (idleWorkers.get() == 0 || workerCount < workers.length) {
for (var i = 0; i < workers.length; i++) {
if (workers[i] == null) {
@@ -575,7 +577,7 @@ public final class ComputerThread implements ComputerScheduler {
/**
* The computer we're currently running.
*/
final AtomicReference<ExecutorImpl> currentExecutor = new AtomicReference<>(null);
final AtomicReference<@Nullable ExecutorImpl> currentExecutor = new AtomicReference<>(null);
/**
* The last time we reported a stack trace, used to avoid spamming the logs.
@@ -584,7 +586,7 @@ public final class ComputerThread implements ComputerScheduler {
WorkerThread(int index) {
this.index = index;
owner = workerFactory.newThread(this);
owner = Objects.requireNonNull(workerFactory.newThread(this), "Cannot create thread");
}
@Override
@@ -8,6 +8,7 @@ import com.google.common.base.Joiner;
import dan200.computercraft.api.filesystem.FileAttributes;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.core.util.Nullability;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
@@ -58,7 +59,7 @@ public class FileMount implements Mount {
if (path.isEmpty() && !created()) return;
try (var stream = Files.newDirectoryStream(resolvePath(path))) {
stream.forEach(x -> contents.add(x.getFileName().toString()));
stream.forEach(x -> contents.add(Nullability.assertNonNull(x.getFileName()).toString()));
} catch (IOException e) {
throw remapException(path, e);
}
@@ -381,7 +381,7 @@ public class FileSystem {
if (part.equals("..")) {
// .. can cancel out the last folder entered
if (outputParts.isEmpty() || outputParts.peekLast().equals("..")) {
if (outputParts.isEmpty() || outputParts.getLast().equals("..")) {
outputParts.addLast("..");
} else {
outputParts.removeLast();
@@ -67,6 +67,8 @@ public final class JarMount extends ArchiveMount<JarMount.FileEntry> implements
if (file.zipEntry == null) throw new FileOperationException(path, NO_SUCH_FILE);
try (var stream = zip.getInputStream(file.zipEntry)) {
if (stream == null) throw new IllegalStateException("Zip entry must exist");
return stream.readAllBytes();
} catch (IOException e) {
// Mask other IO exceptions as a non-existent file.
@@ -200,7 +200,7 @@ public final class MemoryMount extends AbstractInMemoryMount<MemoryMount.FileEnt
private long computeUsedSpace() {
Queue<FileEntry> queue = new ArrayDeque<>();
queue.add(root);
queue.add(Nullability.assertNonNull(root));
long size = 0;
@@ -61,13 +61,22 @@ public class WritableFileMount extends FileMount implements WritableMount {
return capacity - MINIMUM_FILE_SIZE;
}
private File getParentFile(File file) {
if (file.equals(rootFile)) throw new IllegalArgumentException("Cannot navigate above root file");
var parent = file.getParentFile();
if (parent == null) throw new IllegalStateException("File has no parent");
return parent;
}
@Override
public boolean isReadOnly(String path) {
var file = resolveFile(path);
while (true) {
if (file.exists()) return !file.canWrite();
if (file.equals(rootFile)) return false;
file = file.getParentFile();
file = getParentFile(file);
}
}
@@ -81,10 +90,10 @@ public class WritableFileMount extends FileMount implements WritableMount {
}
var dirsToCreate = 1;
var parent = file.getParentFile();
var parent = getParentFile(file);
while (!parent.exists()) {
++dirsToCreate;
parent = parent.getParentFile();
parent = getParentFile(parent);
}
if (getRemainingSpace() < dirsToCreate * MINIMUM_FILE_SIZE) {
@@ -112,8 +121,8 @@ public class WritableFileMount extends FileMount implements WritableMount {
// Empty directories first
if (file.isDirectory()) {
var children = file.list();
for (var aChildren : children) {
deleteRecursively(new File(file, aChildren));
if (children != null) {
for (var aChildren : children) deleteRecursively(new File(file, aChildren));
}
}
@@ -313,7 +313,7 @@ public class CobaltLuaMachine implements ILuaMachine {
static @Nullable Object[] toObjects(Varargs values) {
var count = values.count();
var objects = new Object[count];
var objects = new @Nullable Object[count];
for (var i = 0; i < count; i++) objects[i] = toObject(values.arg(i + 1), null);
return objects;
}
@@ -6,6 +6,7 @@ package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaValues;
import dan200.computercraft.core.util.Nullability;
import org.jetbrains.annotations.VisibleForTesting;
import org.jspecify.annotations.Nullable;
import org.squiddev.cobalt.*;
@@ -59,7 +60,7 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object>
}
}
private LuaValue getImpl(Object o) {
private LuaValue getImpl(@Nullable Object o) {
checkValid();
var value = convertValue(o);
return value == null ? Constants.NIL : table.rawget(value);
@@ -82,13 +83,13 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object>
}
@Override
public boolean containsKey(Object o) {
public boolean containsKey(@Nullable Object o) {
return !getImpl(o).isNil();
}
@Nullable
@Override
public Object get(Object o) {
public Object get(@Nullable Object o) {
return CobaltLuaMachine.toObject(getImpl(o), null);
}
@@ -102,12 +103,12 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object>
checkValid();
if (backingMap != null) return backingMap;
return backingMap = Collections.unmodifiableMap(
Objects.requireNonNull((Map<?, ?>) CobaltLuaMachine.toObject(table, null))
(Map<? extends Object, ? extends Object>) Nullability.assertNonNull(CobaltLuaMachine.toObject(table, null))
);
}
@Override
public boolean containsValue(Object o) {
public boolean containsValue(@Nullable Object o) {
return getBackingMap().containsKey(o);
}
@@ -231,11 +231,10 @@ final class VarargArguments implements IArguments {
}
@SuppressWarnings("ArrayRecordComponent")
private record ArraySlice<T>(T[] array, int offset) {
private record ArraySlice<T>(@Nullable T[] array, int offset) {
// FIXME: We should be able to remove the @Nullables if we update NullAway.
@Nullable
T get(int index) {
@Nullable T get(int index) {
return array[offset + index];
}
@@ -16,7 +16,7 @@ public class LuaUtil {
private static final Set<?> EMPTY_SET = Set.of();
private static final Map<?, ?> EMPTY_MAP = Map.of();
public static Object[] consArray(Object value, Collection<?> rest) {
public static Object[] consArray(Object value, Collection<? extends Object> rest) {
if (rest.isEmpty()) return new Object[]{ value };
// I'm not proud of this code.
@@ -70,11 +70,12 @@ public final class ThreadUtils {
* @see #factory(String)
*/
public static ThreadFactoryBuilder builder(String name) {
var group = new ThreadGroup(baseGroup, baseGroup.getName() + "-" + name);
var fullName = baseGroup.getName() + "-" + name;
var group = new ThreadGroup(baseGroup, fullName);
return new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat(group.getName().replace("%", "%%") + "-%d")
.setUncaughtExceptionHandler((t, e) -> LOG.error("Exception in thread " + t.getName(), e))
.setNameFormat(fullName.replace("%", "%%") + "-%d")
.setUncaughtExceptionHandler((t, e) -> LOG.error("Exception in thread {}", t.getName(), e))
.setThreadFactory(x -> new Thread(group, x));
}
@@ -6,6 +6,7 @@ package dan200.computercraft.test.core;
import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
import org.jspecify.annotations.Nullable;
import java.util.function.Function;
@@ -16,7 +17,7 @@ import java.util.function.Function;
* @param <T> The type of the object to be matched.
* @param <U> The type of the projection/field to be matched.
*/
public final class ContramapMatcher<T, U> extends FeatureMatcher<T, U> {
public final class ContramapMatcher<T, U extends @Nullable Object> extends FeatureMatcher<T, U> {
private final Function<T, U> convert;
public ContramapMatcher(String desc, Function<T, U> convert, Matcher<U> matcher) {
@@ -29,11 +30,11 @@ public final class ContramapMatcher<T, U> extends FeatureMatcher<T, U> {
return convert.apply(actual);
}
public static <T, U> Matcher<T> contramap(Matcher<U> matcher, String desc, Function<T, U> convert) {
public static <T, U extends @Nullable Object> Matcher<T> contramap(Matcher<U> matcher, String desc, Function<T, U> convert) {
return new ContramapMatcher<>(desc, convert, matcher);
}
public static <T, U> Matcher<T> contramap(Matcher<U> matcher, Function<T, U> convert) {
public static <T, U extends @Nullable Object> Matcher<T> contramap(Matcher<U> matcher, Function<T, U> convert) {
return new ContramapMatcher<>("-f(_)->", convert, matcher);
}
}
@@ -6,6 +6,7 @@ package dan200.computercraft.test.core;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.jspecify.annotations.Nullable;
import java.util.List;
import java.util.Map;
@@ -53,7 +54,7 @@ public class CustomMatchers {
* @param <V> The type of values in the map.
* @return A matcher that projects out of a map.
*/
public static <K, V> Matcher<Map<? extends K, ? extends V>> containsEntryWith(K key, Matcher<? super V> value) {
public static <K, V extends @Nullable Object> Matcher<Map<? extends K, ? extends V>> containsEntryWith(K key, Matcher<? super V> value) {
return ContramapMatcher.contramap(value, new StringDescription().appendValue(key).toString(), x -> x.get(key));
}
}
@@ -69,7 +69,7 @@ final class StructuralEqualities {
}
}
static final class FeatureEquality<T, U> implements StructuralEquality<T> {
static final class FeatureEquality<T, U extends @Nullable Object> implements StructuralEquality<T> {
private final String desc;
private final Function<T, U> get;
private final StructuralEquality<? super U> inner;
@@ -100,7 +100,9 @@ final class StructuralEqualities {
}
}
record ListEquality<T>(StructuralEquality<T> equality) implements StructuralEquality<List<T>> {
record ListEquality<T extends @Nullable Object>(
StructuralEquality<T> equality
) implements StructuralEquality<List<T>> {
@Override
public boolean equals(List<T> left, List<T> right) {
if (left.size() != right.size()) return false;
@@ -111,6 +113,7 @@ final class StructuralEqualities {
}
@Override
@SuppressWarnings("NullAway") // FIXME: See https://github.com/uber/NullAway/issues/1727
public void describe(Description description, List<T> object) {
description.appendText("[");
var separator = false;
@@ -123,7 +126,7 @@ final class StructuralEqualities {
}
}
static final class EqualityMatcher<T> extends TypeSafeMatcher<T> {
static final class EqualityMatcher<T extends @Nullable Object> extends TypeSafeMatcher<@Nullable T> {
private final StructuralEquality<T> equality;
private final T equalTo;
@@ -6,6 +6,7 @@ package dan200.computercraft.test.core;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.List;
@@ -18,7 +19,7 @@ import java.util.function.Function;
*
* @param <T> The type of the value to check.
*/
public interface StructuralEquality<T> {
public interface StructuralEquality<T extends @Nullable Object> {
/**
* Check if two non-null values are equal.
*
@@ -52,7 +53,7 @@ public interface StructuralEquality<T> {
* @param expected The expected value.
* @return A matcher which checks if its input is equal to the given value.
*/
default Matcher<T> asMatcher(Class<T> klass, T expected) {
default Matcher<@Nullable T> asMatcher(Class<T> klass, T expected) {
return new StructuralEqualities.EqualityMatcher<>(klass, this, expected);
}
@@ -117,8 +118,7 @@ public interface StructuralEquality<T> {
* @param <U> The type of the field's.
* @return The newly created {@link StructuralEquality} object.
*/
@SuppressWarnings("unchecked")
static <T, U> StructuralEquality<T> field(Class<T> klass, String fieldName, StructuralEquality<U> inner) {
static <T, U extends @Nullable Object> StructuralEquality<T> field(Class<T> klass, String fieldName, StructuralEquality<U> inner) {
Field field;
try {
field = klass.getDeclaredField(fieldName);
@@ -127,13 +127,18 @@ public interface StructuralEquality<T> {
throw new IllegalArgumentException("Cannot find field", e);
}
return new StructuralEqualities.FeatureEquality<>(fieldName, x -> {
return new StructuralEqualities.FeatureEquality<>(fieldName, makeFieldReader(field), inner);
}
@SuppressWarnings({ "unchecked", "NullAway" })
private static <T, U extends @Nullable Object> Function<T, U> makeFieldReader(Field field) {
return x -> {
try {
return (U) field.get(x);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Cannot read field", e);
}
}, inner);
};
}
/**
@@ -13,6 +13,7 @@ import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount;
import dan200.computercraft.core.filesystem.MemoryMount;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.core.util.Nullability;
import org.jspecify.annotations.Nullable;
import java.io.File;
@@ -75,7 +76,7 @@ public class BasicEnvironment implements ComputerEnvironment, GlobalEnvironment
@Override
public @Nullable InputStream getLuaBios() {
return ComputerContext.class.getClassLoader().getResourceAsStream("data/computercraft/lua/bios.lua");
return Nullability.assertNonNull(ComputerContext.class.getClassLoader()).getResourceAsStream("data/computercraft/lua/bios.lua");
}
public static Mount createMount(Class<?> klass, String path, String fallback) {
@@ -104,7 +105,13 @@ public class BasicEnvironment implements ComputerEnvironment, GlobalEnvironment
}
private static File getContainingFile(Class<?> klass) {
var path = klass.getProtectionDomain().getCodeSource().getLocation().getPath();
var source = klass.getProtectionDomain().getCodeSource();
if (source == null) throw new IllegalStateException("Cannot find owning Jar");
var location = source.getLocation();
if (location == null) throw new IllegalStateException("Cannot find owning Jar");
var path = location.getPath();
var bangIndex = path.indexOf("!");
// Plain old file, so step up from dan200.computercraft.
@@ -21,11 +21,13 @@ import org.jspecify.annotations.Nullable;
public final class MediaLookup {
public static final ResourceLocation ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "media");
@SuppressWarnings("NullAway") // FIXME(26.3): Can be removed when FAPI fixes their annotation
private static final ItemApiLookup<IMedia, @Nullable Void> lookup = ItemApiLookup.get(ID, IMedia.class, Void.class);
private MediaLookup() {
}
@SuppressWarnings("NullAway") // FIXME(26.3): Can be removed when FAPI fixes their annotation
public static ItemApiLookup<IMedia, @Nullable Void> get() {
return lookup;
}
@@ -4,6 +4,7 @@
package dan200.computercraft.client.integration.rei;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.integration.UpgradeRecipeGenerator;
import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator;
import me.shedaniel.rei.api.common.entry.EntryStack;
@@ -17,7 +18,9 @@ import java.util.Optional;
* Provides custom recipe and usage hints for pocket/turtle upgrades.
*/
class UpgradeDisplayGenerator implements DynamicDisplayGenerator<DefaultCraftingDisplay<?>> {
private final UpgradeRecipeGenerator<DefaultCraftingDisplay<?>> resolver = new UpgradeRecipeGenerator<>(DefaultCraftingDisplay::of);
private final UpgradeRecipeGenerator<DefaultCraftingDisplay<? extends Object>> resolver = new UpgradeRecipeGenerator<>(
r -> Nullability.assertNonNull(DefaultCraftingDisplay.of(r))
);
@Override
public Optional<List<DefaultCraftingDisplay<?>>> getRecipeFor(EntryStack<?> entry) {
@@ -53,7 +53,7 @@ public final class FoiledModel extends ForwardingBakedModel implements RenderCon
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
return this == obj || (obj instanceof FoiledModel other && wrapped.equals(other.wrapped));
}
@@ -141,6 +141,7 @@ public class ComputerCraft {
}
}
@SuppressWarnings("NullAway") // FIXME(26.3): Can be removed when FAPI fixes their annotation
private record ItemComponentImpl<T>(
ItemApiLookup<T, @Nullable Void> lookup
) implements ModRegistry.ItemComponent<T> {
@@ -41,7 +41,7 @@ public final class InventoryMethods extends AbstractInventoryMethods<InventoryMe
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof StorageWrapper other)) return false;
@@ -95,7 +95,6 @@ public final class InventoryMethods extends AbstractInventoryMethods<InventoryMe
return storage == null ? null : new StorageWrapper(storage);
}
@SuppressWarnings("NullAway") // FIXME: Doesn't cope with @Nullable type parameter.
private static @Nullable Storage<ItemVariant> extractContainerImpl(Level level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) {
var internal = ItemStorage.SIDED.find(level, pos, state, blockEntity, null);
if (internal != null) return internal;
@@ -22,6 +22,7 @@ import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -52,7 +53,9 @@ public final class FabricConfigFile extends ConfigFile {
.build();
try {
Files.createDirectories(path.getParent());
var parent = path.getParent();
if (parent != null) Files.createDirectories(parent);
FileWatcher.defaultInstance().addWatch(config.getNioPath(), this::loadConfig);
} catch (IOException e) {
LOG.error("Failed to load config from {}.", path, e);
@@ -153,7 +156,8 @@ public final class FabricConfigFile extends ConfigFile {
var path = getPath(name);
spec.define(path, defaultValue, o -> o != null && o != NullObject.NULL_OBJECT && EnumGetMethod.NAME_IGNORECASE.validate(o, defaultValue.getDeclaringClass()));
var suffix = "Allowed Values: " + Arrays.stream(defaultValue.getDeclaringClass().getEnumConstants()).map(Enum::name).collect(Collectors.joining(", "));
var suffix = "Allowed Values: " + Arrays.stream(Objects.requireNonNull(defaultValue.getDeclaringClass().getEnumConstants()))
.map(Enum::name).collect(Collectors.joining(", "));
return defineValue(name, takeComment(), suffix, defaultValue, (c, p, d) -> c.getEnumOrElse(p, d, EnumGetMethod.NAME_IGNORECASE));
}
@@ -39,7 +39,7 @@ public final class ForgeComputerCraftAPI {
* @param capability The capability to register.
* @see GenericSource
*/
public static void registerGenericCapability(Capability<?> capability) {
public static void registerGenericCapability(Capability<? extends Object> capability) {
getInstance().registerGenericCapability(capability);
}
@@ -31,7 +31,7 @@ public interface ComputerCraftAPIForgeService extends ComputerCraftAPIService {
void registerPeripheralProvider(IPeripheralProvider provider);
void registerGenericCapability(Capability<?> capability);
void registerGenericCapability(Capability<? extends Object> capability);
LazyOptional<WiredElement> getWiredElementAt(BlockGetter world, BlockPos pos, Direction side);
@@ -62,7 +62,7 @@ public final class ForgeClientRegistry {
@SubscribeEvent
public static void registerShaders(RegisterShadersEvent event) throws IOException {
ClientRegistry.registerShaders(event.getResourceProvider(), event::registerShader);
ClientRegistry.registerShaders(event.getResourceProvider(), (s, c) -> event.registerShader(s, c));
}
@SubscribeEvent
@@ -39,7 +39,7 @@ public final class FoiledModel extends BakedModelWrapper<BakedModel> {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
return this == obj || (obj instanceof FoiledModel other && originalModel.equals(other.originalModel));
}
@@ -31,7 +31,7 @@ import java.util.Arrays;
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
private static final RandomSource random = RandomSource.create(0);
private static final Direction[] directions = Arrays.copyOf(Direction.values(), 7);
private static final @Nullable Direction[] directions = Arrays.copyOf(Direction.values(), 7);
@Override
public BakedModel getModel(ModelManager manager, ResourceLocation location) {
@@ -43,10 +43,14 @@ import net.minecraftforge.fml.event.config.ModConfigEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.registries.NewRegistryEvent;
import net.minecraftforge.registries.RegistryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Mod(ComputerCraftAPI.MOD_ID)
@Mod.EventBusSubscriber(modid = ComputerCraftAPI.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public final class ComputerCraft {
private static final Logger LOG = LoggerFactory.getLogger(ComputerCraft.class);
public ComputerCraft() {
ModRegistry.register();
@@ -135,8 +139,11 @@ public final class ComputerCraft {
Pack.Position.BOTTOM,
PackSource.FEATURE
);
event.addRepositorySource(add -> add.accept(pack));
if (pack == null) {
LOG.error("Cannot find " + TurtleStorage.DATA_PACK_NAME + " datapack");
} else {
event.addRepositorySource(add -> add.accept(pack));
}
}
}
}
@@ -43,7 +43,7 @@ public final class ComputerCraftAPIImpl extends AbstractComputerCraftAPI impleme
}
@Override
public void registerGenericCapability(Capability<?> capability) {
public void registerGenericCapability(Capability<? extends Object> capability) {
Peripherals.registerGenericCapability(capability);
}
@@ -59,14 +59,13 @@ public final class Peripherals {
* @param <T> The type of the capability we look up.
*/
private record CapabilityLookup<T>(Capability<T> capability) implements ComponentLookup<InvalidateCallback> {
@Nullable
@Override
public T find(ServerLevel level, BlockPos pos, BlockState state, BlockEntity blockEntity, Direction side, InvalidateCallback invalidate) {
public @Nullable T find(ServerLevel level, BlockPos pos, BlockState state, BlockEntity blockEntity, Direction side, InvalidateCallback invalidate) {
return CapabilityUtil.unwrap(CapabilityUtil.getCapability(blockEntity, this.capability(), side), invalidate);
}
}
public static void registerGenericCapability(Capability<?> capability) {
public static void registerGenericCapability(Capability<? extends Object> capability) {
Objects.requireNonNull(capability, "Capability cannot be null");
registerGenericLookup(new CapabilityLookup<>(capability));
}
@@ -29,17 +29,17 @@ import java.util.function.Supplier;
*/
public final class CapabilityProvider<T> implements ICapabilityProvider {
private final Capability<T> cap;
private final Supplier<T> supplier;
private final Supplier<@Nullable T> supplier;
private final BooleanSupplier isRemoved;
private @Nullable LazyOptional<T> instance;
private CapabilityProvider(Capability<T> cap, Supplier<T> supplier, BooleanSupplier isRemoved) {
private CapabilityProvider(Capability<T> cap, Supplier<@Nullable T> supplier, BooleanSupplier isRemoved) {
this.cap = Objects.requireNonNull(cap, "Capability cannot be null");
this.supplier = supplier;
this.isRemoved = isRemoved;
}
public static <T> CapabilityProvider<T> attach(AttachCapabilitiesEvent<?> event, ResourceLocation id, Capability<T> cap, Supplier<T> instance) {
public static <T> CapabilityProvider<T> attach(AttachCapabilitiesEvent<?> event, ResourceLocation id, Capability<T> cap, Supplier<@Nullable T> instance) {
BooleanSupplier isRemoved
= event.getObject() instanceof BlockEntity be ? be::isRemoved
: event.getObject() instanceof Entity entity ? entity::isRemoved
@@ -4,12 +4,14 @@
package cc.tweaked.standalone;
import dan200.computercraft.core.util.Nullability;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL45C;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Deque;
@@ -79,6 +81,12 @@ public class GLObjects implements AutoCloseable {
return texture;
}
private InputStream getResource(String path) throws IOException {
var stream = Nullability.assertNonNull(getClass().getClassLoader()).getResourceAsStream(path);
if (stream == null) throw new IOException("Cannot find " + path);
return stream;
}
/**
* Create a texture, loading it from an image.
*
@@ -88,8 +96,7 @@ public class GLObjects implements AutoCloseable {
*/
public int loadTexture(String path) throws IOException {
BufferedImage image;
try (var stream = getClass().getClassLoader().getResourceAsStream(path)) {
if (stream == null) throw new IOException("Cannot find " + path);
try (var stream = getResource(path)) {
image = ImageIO.read(stream);
}
@@ -122,8 +129,7 @@ public class GLObjects implements AutoCloseable {
*/
public int compileShader(int type, String path) throws IOException {
String contents;
try (var stream = getClass().getClassLoader().getResourceAsStream(path)) {
if (stream == null) throw new IOException("Could not load shader " + path);
try (var stream = getResource(path)) {
contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
}
@@ -127,7 +127,8 @@ public class InputState {
continue;
}
files.add(new TransferredFile(path.getFileName().toString(), new ArrayByteChannel(contents)));
var name = path.getFileName();
files.add(new TransferredFile(name == null ? "unknown" : name.toString(), new ArrayByteChannel(contents)));
}
if (!files.isEmpty()) computer.queueEvent(TransferredFiles.EVENT, new Object[]{ new TransferredFiles(files) });
@@ -74,7 +74,9 @@ public class Main {
var matcher = TermSize.PATTERN.matcher(value);
if (!matcher.matches()) throw new ParseException("'" + value + "' is not a valid terminal size.");
return new TermSize(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
var width = Integer.parseInt(Objects.requireNonNull(matcher.group(1)));
var height = Integer.parseInt(Objects.requireNonNull(matcher.group(2)));
return new TermSize(width, height);
}
}
@@ -85,7 +87,9 @@ public class Main {
var matcher = MountPaths.PATTERN.matcher(value);
if (!matcher.matches()) throw new ParseException("'" + value + "' is not a mount spec.");
return new MountPaths(parsePath(matcher.group(1)), matcher.group(2));
var src = parsePath(Objects.requireNonNull(matcher.group(1)));
var dest = Objects.requireNonNull(matcher.group(2));
return new MountPaths(src, dest);
}
}
@@ -23,6 +23,7 @@ import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
@@ -37,7 +38,7 @@ public class Builder {
var input = getPath(scope, "cct.input");
var classpath = getPath(scope, "cct.classpath");
var output = getFile("cct.output");
var version = System.getProperty("cct.version");
var version = Objects.requireNonNull(System.getProperty("cct.version"), "cct.version must be present");
var minify = Boolean.parseBoolean(System.getProperty("cct.minify", "true"));
buildClasses(input, classpath, output, minify);
@@ -73,7 +74,7 @@ public class Builder {
run(output, remapper, TeaVMTargetType.JAVASCRIPT, TeaVMOptimizationLevel.ADVANCED, minify);
run(output, remapper, TeaVMTargetType.WEBASSEMBLY_GC, TeaVMOptimizationLevel.SIMPLE, minify);
try (var runtime = Builder.class.getClassLoader().getResourceAsStream("org/teavm/backend/wasm/wasm-gc-module-runtime.js")) {
try (var runtime = Objects.requireNonNull(Builder.class.getClassLoader()).getResourceAsStream("org/teavm/backend/wasm/wasm-gc-module-runtime.js")) {
if (runtime == null) throw new IllegalStateException("Cannot find WASM runtime");
Files.copy(runtime, output.resolve("wasm-gc-runtime.js"), StandardCopyOption.REPLACE_EXISTING);
}
@@ -143,7 +144,9 @@ public class Builder {
}
private static List<Path> getPath(CloseScope scope, String name) {
return Arrays.stream(System.getProperty(name).split(File.pathSeparator))
var path = System.getProperty(name);
if (path == null) throw new NullPointerException(name + " must be given");
return Arrays.stream(path.split(File.pathSeparator))
.map(Path::of)
.filter(Files::exists)
.map(file -> {
@@ -157,6 +160,8 @@ public class Builder {
}
private static Path getFile(String name) {
return Path.of(System.getProperty(name));
var path = System.getProperty(name);
if (path == null) throw new NullPointerException(name + " must be given");
return Path.of(path);
}
}
@@ -14,7 +14,10 @@ import org.teavm.jso.core.JSString;
/**
* Utility methods for converting between Java and Javascript representations.
*/
public class JavascriptConv {
public final class JavascriptConv {
private JavascriptConv() {
}
/**
* Convert an array of Javascript values to an equivalent array of Java values.
*
@@ -24,7 +27,7 @@ public class JavascriptConv {
@Contract("null -> null; !null -> !null")
public static @Nullable Object @Nullable [] toJava(@Nullable JSObject @Nullable [] value) {
if (value == null) return null;
var out = new Object[value.length];
var out = new @Nullable Object[value.length];
for (var i = 0; i < value.length; i++) out[i] = toJava(value[i]);
return out;
}
@@ -12,6 +12,7 @@ import com.google.common.reflect.TypeToken;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.asm.Reflect;
import dan200.computercraft.core.methods.LuaMethod;
import dan200.computercraft.core.util.Nullability;
import org.jspecify.annotations.Nullable;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
@@ -106,7 +107,7 @@ public final class StaticGenerator<T> {
}
}
var annotation = method.getAnnotation(LuaFunction.class);
var annotation = Nullability.assertNonNull(method.getAnnotation(LuaFunction.class));
if (annotation.unsafe() && annotation.mainThread()) {
System.err.printf("Lua Method %s cannot use unsafe and mainThread.\n", name);
return Optional.empty();