diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMLoweringErrorCode.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMLoweringErrorCode.java index f0a6cc34..b547a710 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMLoweringErrorCode.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMLoweringErrorCode.java @@ -2,6 +2,8 @@ package p.studio.compiler.backend.irvm; public enum IRVMLoweringErrorCode { LOWER_IRVM_EMPTY_EXECUTABLE_INPUT, + LOWER_IRVM_ENTRYPOINT_MISSING, + LOWER_IRVM_ENTRYPOINT_AMBIGUOUS, LOWER_IRVM_MISSING_CALLEE, LOWER_IRVM_INVALID_INTRINSIC_ID, LOWER_IRVM_CALL_ARG_SLOTS_MISMATCH, diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java index 5cfb7f8e..aacab4e9 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java @@ -217,13 +217,20 @@ public class LowerToIRVMService { final ReadOnlyList functions) { final var sorted = new ArrayList<>(functions.asList()); sorted.sort(FUNCTION_ORDER); - var entrypoint = sorted.getFirst(); - for (final var candidate : sorted) { - if ("main".equals(candidate.callableName())) { - entrypoint = candidate; - break; - } + final var entrypoints = sorted.stream() + .filter(candidate -> "main".equals(candidate.callableName())) + .toList(); + if (entrypoints.isEmpty()) { + throw new IRVMLoweringException( + IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING, + "missing entrypoint callable 'main'"); } + if (entrypoints.size() > 1) { + throw new IRVMLoweringException( + IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS, + "ambiguous entrypoint: found %d callables named 'main'".formatted(entrypoints.size())); + } + final var entrypoint = entrypoints.getFirst(); final var ordered = new ArrayList(sorted.size()); ordered.add(entrypoint); for (final var fn : sorted) { diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java index 722f2871..cb70343f 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java @@ -130,6 +130,29 @@ class LowerToIRVMServiceTest { assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_MISSING_JUMP_TARGET, thrown.code()); } + @Test + void lowerMustRejectWhenEntrypointIsMissing() { + final var backend = IRBackend.builder() + .executableFunctions(ReadOnlyList.from( + fn("helper", "app", 10, ReadOnlyList.from(ret())))) + .build(); + + final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend)); + assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING, thrown.code()); + } + + @Test + void lowerMustRejectWhenEntrypointIsAmbiguous() { + final var backend = IRBackend.builder() + .executableFunctions(ReadOnlyList.from( + fn("main", "app", 10, ReadOnlyList.from(ret())), + fn("main", "sdk", 11, ReadOnlyList.from(ret())))) + .build(); + + final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend)); + assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS, thrown.code()); + } + private static IRBackendExecutableFunction fn( final String name, final String moduleKey,