implements PR-19.4 global semantics identity and collision validation
This commit is contained in:
parent
738eea71ee
commit
71a993ad4a
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -168,6 +168,7 @@ final class PbsFlowSemanticSupport {
|
||||
final Map<String, Set<String>> enums = new HashMap<>();
|
||||
final Map<String, Set<String>> errors = new HashMap<>();
|
||||
final Map<String, TypeView> constTypes = new HashMap<>();
|
||||
final Map<String, TypeView> globalTypes = new HashMap<>();
|
||||
final Map<String, TypeView> serviceSingletons = new HashMap<>();
|
||||
final Set<String> knownStructNames = new HashSet<>();
|
||||
final Set<String> 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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()));
|
||||
|
||||
@ -19,6 +19,7 @@ final class PbsNamespaceBinder {
|
||||
private final Map<NameId, Span> valueNamespace = new HashMap<>();
|
||||
private final Map<NameId, Span> hostOwnerNamespace = new HashMap<>();
|
||||
private final Map<CallableIdentity, Span> callableNamespace = new HashMap<>();
|
||||
private final Map<NameId, VisibleTopLevelSymbol> 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) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 = """
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user