diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationRuleValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationRuleValidator.java index 15d1f727..efeb37c8 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationRuleValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationRuleValidator.java @@ -160,6 +160,79 @@ final class PbsDeclarationRuleValidator { } } + void validateGlobalDeclaration(final PbsAst.GlobalDecl globalDecl) { + if (globalDecl.explicitType() == null) { + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_MISSING_GLOBAL_TYPE_ANNOTATION.name(), + "Global declaration '%s' must include an explicit type annotation".formatted(globalDecl.name()), + globalDecl.span()); + } else { + PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive( + List.of(globalDecl.explicitType()), + "global '%s'".formatted(globalDecl.name()), + false, + diagnostics); + } + + if (globalDecl.initializer() == null) { + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_MISSING_GLOBAL_INITIALIZER.name(), + "Global declaration '%s' must include an initializer".formatted(globalDecl.name()), + globalDecl.span()); + return; + } + + final var invalidSpan = firstUnsupportedGlobalInitializerSpan(globalDecl.initializer()); + if (invalidSpan != null) { + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_GLOBAL_UNSUPPORTED_INITIALIZER.name(), + "Global initializer for '%s' uses an unsupported form".formatted(globalDecl.name()), + invalidSpan); + } + } + + private Span firstUnsupportedGlobalInitializerSpan(final PbsAst.Expression expression) { + if (expression == null) { + return null; + } + if (expression instanceof PbsAst.IntLiteralExpr + || expression instanceof PbsAst.FloatLiteralExpr + || expression instanceof PbsAst.BoundedLiteralExpr + || expression instanceof PbsAst.StringLiteralExpr + || expression instanceof PbsAst.BoolLiteralExpr + || expression instanceof PbsAst.IdentifierExpr + || expression instanceof PbsAst.ThisExpr + || expression instanceof PbsAst.UnitExpr) { + return null; + } + if (expression instanceof PbsAst.GroupExpr groupExpr) { + return firstUnsupportedGlobalInitializerSpan(groupExpr.expression()); + } + if (expression instanceof PbsAst.UnaryExpr unaryExpr) { + return firstUnsupportedGlobalInitializerSpan(unaryExpr.expression()); + } + if (expression instanceof PbsAst.BinaryExpr binaryExpr) { + final var left = firstUnsupportedGlobalInitializerSpan(binaryExpr.left()); + if (left != null) { + return left; + } + return firstUnsupportedGlobalInitializerSpan(binaryExpr.right()); + } + if (expression instanceof PbsAst.MemberExpr memberExpr) { + return firstUnsupportedGlobalInitializerSpan(memberExpr.receiver()); + } + if (expression instanceof PbsAst.NewExpr newExpr) { + for (final var argument : newExpr.arguments()) { + final var invalid = firstUnsupportedGlobalInitializerSpan(argument); + if (invalid != null) { + return invalid; + } + } + return null; + } + return expression.span(); + } + private void validateReturnSurface( final PbsAst.ReturnKind returnKind, final PbsAst.TypeRef returnType, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java index 61e9fa79..88eb2843 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java @@ -61,6 +61,7 @@ public final class PbsDeclarationSemanticsValidator { false, binder, rules); + binder.registerVisibleTopLevelSurface(functionDecl.name(), functionDecl.span(), "function"); continue; } @@ -79,6 +80,7 @@ public final class PbsDeclarationSemanticsValidator { if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) { binder.registerType(serviceDecl.name(), serviceDecl.span(), "service"); binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton"); + binder.registerVisibleTopLevelSurface(serviceDecl.name(), serviceDecl.span(), "service"); validateServiceDeclaration(serviceDecl, binder, rules); continue; } @@ -124,8 +126,16 @@ public final class PbsDeclarationSemanticsValidator { continue; } + if (topDecl instanceof PbsAst.GlobalDecl globalDecl) { + binder.registerValue(globalDecl.name(), globalDecl.span(), "global"); + binder.registerVisibleTopLevelSurface(globalDecl.name(), globalDecl.span(), "global"); + rules.validateGlobalDeclaration(globalDecl); + continue; + } + if (topDecl instanceof PbsAst.ConstDecl constDecl) { binder.registerValue(constDecl.name(), constDecl.span(), "const"); + binder.registerVisibleTopLevelSurface(constDecl.name(), constDecl.span(), "const"); final var allowMissingInitializer = validateConstDeclarationAttributes(constDecl, interfaceModule, diagnostics); rules.validateConstDeclaration(constDecl, !allowMissingInitializer); continue; diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java index 596fcd46..f4fc132c 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java @@ -168,6 +168,7 @@ final class PbsFlowSemanticSupport { final Map> enums = new HashMap<>(); final Map> errors = new HashMap<>(); final Map constTypes = new HashMap<>(); + final Map globalTypes = new HashMap<>(); final Map serviceSingletons = new HashMap<>(); final Set knownStructNames = new HashSet<>(); final Set knownServiceNames = new HashSet<>(); @@ -369,6 +370,10 @@ final class PbsFlowSemanticSupport { } if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) { constTypes.put(constDecl.name(), typeFrom(constDecl.explicitType())); + return; + } + if (topDecl instanceof PbsAst.GlobalDecl globalDecl && globalDecl.explicitType() != null) { + globalTypes.put(globalDecl.name(), typeFrom(globalDecl.explicitType())); } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStructuralExpressionAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStructuralExpressionAnalyzer.java index 1d561a30..1ca7a0bc 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStructuralExpressionAnalyzer.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStructuralExpressionAnalyzer.java @@ -85,6 +85,11 @@ final class PbsFlowStructuralExpressionAnalyzer { return ExprResult.type(constType); } + final var globalType = model.globalTypes.get(identifierExpr.name()); + if (globalType != null) { + return ExprResult.type(globalType); + } + final var callbackSignature = model.callbacks.get(identifierExpr.name()); if (callbackSignature != null) { return ExprResult.type(TypeView.callback(identifierExpr.name(), callbackSignature.inputTypes(), callbackSignature.outputType())); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsNamespaceBinder.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsNamespaceBinder.java index 8ee52873..af217fb0 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsNamespaceBinder.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsNamespaceBinder.java @@ -19,6 +19,7 @@ final class PbsNamespaceBinder { private final Map valueNamespace = new HashMap<>(); private final Map hostOwnerNamespace = new HashMap<>(); private final Map callableNamespace = new HashMap<>(); + private final Map visibleTopLevelNamespace = new HashMap<>(); PbsNamespaceBinder(final NameTable nameTable, final DiagnosticSink diagnostics) { this.nameTable = nameTable; @@ -33,6 +34,24 @@ final class PbsNamespaceBinder { registerByNamespace(valueNamespace, name, span, "value namespace", kind); } + void registerVisibleTopLevelSurface(final String name, final Span span, final String kind) { + final var nameId = nameTable.register(name); + final var first = visibleTopLevelNamespace.putIfAbsent(nameId, new VisibleTopLevelSymbol(kind, span)); + if (first == null) { + return; + } + if ("function".equals(kind) && "function".equals(first.kind())) { + return; + } + + p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, + Severity.Error, + PbsSemanticsErrors.E_SEM_DUPLICATE_DECLARATION.name(), + "Visible top-level symbol '%s' collides between %s and %s".formatted(name, kind, first.kind()), + span, + List.of(new RelatedSpan("First visible top-level symbol is here", first.span()))); + } + @SuppressWarnings("unused") void registerHostOwner(final String name, final Span span, final String kind) { registerByNamespace(hostOwnerNamespace, name, span, "host-owner namespace", kind); @@ -103,4 +122,9 @@ final class PbsNamespaceBinder { NameId callableNameId, String shape) { } + + private record VisibleTopLevelSymbol( + String kind, + Span span) { + } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java index 13891f49..68f26954 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java @@ -17,6 +17,9 @@ public enum PbsSemanticsErrors { E_SEM_MALFORMED_RESERVED_ATTRIBUTE, E_SEM_MISSING_CONST_TYPE_ANNOTATION, E_SEM_MISSING_CONST_INITIALIZER, + E_SEM_MISSING_GLOBAL_TYPE_ANNOTATION, + E_SEM_MISSING_GLOBAL_INITIALIZER, + E_SEM_GLOBAL_UNSUPPORTED_INITIALIZER, E_SEM_CONST_NON_CONSTANT_INITIALIZER, E_SEM_CONST_INITIALIZER_TYPE_MISMATCH, E_SEM_CONST_CYCLIC_DEPENDENCY, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java index ab7cee86..4973e389 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java @@ -144,6 +144,65 @@ class PbsSemanticsDeclarationsTest { d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_DECLARATION.name()))); } + @Test + void shouldRejectVisibleTopLevelCollisionsBetweenFunctionServiceGlobalAndConst() { + final var source = """ + fn Tick() -> int { return 1; } + declare service Tick { + fn run() -> void { return; } + } + declare global Score: int = 0; + declare const Score: int = 1; + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics); + + final var duplicateCount = diagnostics.stream() + .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_DECLARATION.name())) + .count(); + assertTrue(duplicateCount >= 2); + } + + @Test + void shouldAcceptSupportedGlobalInitializerForms() { + final var source = """ + declare struct Box(value: int); + declare global BASE: int = 1; + declare global CLONE: int = BASE + 1; + declare global DEFAULT_BOX: Box = new Box(BASE); + + fn read() -> int { + return CLONE; + } + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics); + + assertFalse(diagnostics.stream().anyMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_GLOBAL_UNSUPPORTED_INITIALIZER.name()))); + } + + @Test + void shouldRejectUnsupportedGlobalInitializerForms() { + final var source = """ + fn inc(v: int) -> int { return v + 1; } + declare global A: int = inc(1); + declare global B: int = if true { 1 } else { 2 }; + declare global C: optional int = some(1); + declare global D: optional int = none; + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics); + + final var invalidCount = diagnostics.stream() + .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_GLOBAL_UNSUPPORTED_INITIALIZER.name())) + .count(); + assertEquals(4, invalidCount); + } + @Test void shouldAllowSelfInStructServiceMethodsAndCtors() { final var source = """