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(
|
private void validateReturnSurface(
|
||||||
final PbsAst.ReturnKind returnKind,
|
final PbsAst.ReturnKind returnKind,
|
||||||
final PbsAst.TypeRef returnType,
|
final PbsAst.TypeRef returnType,
|
||||||
|
|||||||
@ -61,6 +61,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
false,
|
false,
|
||||||
binder,
|
binder,
|
||||||
rules);
|
rules);
|
||||||
|
binder.registerVisibleTopLevelSurface(functionDecl.name(), functionDecl.span(), "function");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +80,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
binder.registerType(serviceDecl.name(), serviceDecl.span(), "service");
|
binder.registerType(serviceDecl.name(), serviceDecl.span(), "service");
|
||||||
binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton");
|
binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton");
|
||||||
|
binder.registerVisibleTopLevelSurface(serviceDecl.name(), serviceDecl.span(), "service");
|
||||||
validateServiceDeclaration(serviceDecl, binder, rules);
|
validateServiceDeclaration(serviceDecl, binder, rules);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -124,8 +126,16 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
continue;
|
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) {
|
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||||
binder.registerValue(constDecl.name(), constDecl.span(), "const");
|
binder.registerValue(constDecl.name(), constDecl.span(), "const");
|
||||||
|
binder.registerVisibleTopLevelSurface(constDecl.name(), constDecl.span(), "const");
|
||||||
final var allowMissingInitializer = validateConstDeclarationAttributes(constDecl, interfaceModule, diagnostics);
|
final var allowMissingInitializer = validateConstDeclarationAttributes(constDecl, interfaceModule, diagnostics);
|
||||||
rules.validateConstDeclaration(constDecl, !allowMissingInitializer);
|
rules.validateConstDeclaration(constDecl, !allowMissingInitializer);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@ -168,6 +168,7 @@ final class PbsFlowSemanticSupport {
|
|||||||
final Map<String, Set<String>> enums = new HashMap<>();
|
final Map<String, Set<String>> enums = new HashMap<>();
|
||||||
final Map<String, Set<String>> errors = new HashMap<>();
|
final Map<String, Set<String>> errors = new HashMap<>();
|
||||||
final Map<String, TypeView> constTypes = new HashMap<>();
|
final Map<String, TypeView> constTypes = new HashMap<>();
|
||||||
|
final Map<String, TypeView> globalTypes = new HashMap<>();
|
||||||
final Map<String, TypeView> serviceSingletons = new HashMap<>();
|
final Map<String, TypeView> serviceSingletons = new HashMap<>();
|
||||||
final Set<String> knownStructNames = new HashSet<>();
|
final Set<String> knownStructNames = new HashSet<>();
|
||||||
final Set<String> knownServiceNames = new HashSet<>();
|
final Set<String> knownServiceNames = new HashSet<>();
|
||||||
@ -369,6 +370,10 @@ final class PbsFlowSemanticSupport {
|
|||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) {
|
if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) {
|
||||||
constTypes.put(constDecl.name(), typeFrom(constDecl.explicitType()));
|
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);
|
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());
|
final var callbackSignature = model.callbacks.get(identifierExpr.name());
|
||||||
if (callbackSignature != null) {
|
if (callbackSignature != null) {
|
||||||
return ExprResult.type(TypeView.callback(identifierExpr.name(), callbackSignature.inputTypes(), callbackSignature.outputType()));
|
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> valueNamespace = new HashMap<>();
|
||||||
private final Map<NameId, Span> hostOwnerNamespace = new HashMap<>();
|
private final Map<NameId, Span> hostOwnerNamespace = new HashMap<>();
|
||||||
private final Map<CallableIdentity, Span> callableNamespace = 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) {
|
PbsNamespaceBinder(final NameTable nameTable, final DiagnosticSink diagnostics) {
|
||||||
this.nameTable = nameTable;
|
this.nameTable = nameTable;
|
||||||
@ -33,6 +34,24 @@ final class PbsNamespaceBinder {
|
|||||||
registerByNamespace(valueNamespace, name, span, "value namespace", kind);
|
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")
|
@SuppressWarnings("unused")
|
||||||
void registerHostOwner(final String name, final Span span, final String kind) {
|
void registerHostOwner(final String name, final Span span, final String kind) {
|
||||||
registerByNamespace(hostOwnerNamespace, name, span, "host-owner namespace", kind);
|
registerByNamespace(hostOwnerNamespace, name, span, "host-owner namespace", kind);
|
||||||
@ -103,4 +122,9 @@ final class PbsNamespaceBinder {
|
|||||||
NameId callableNameId,
|
NameId callableNameId,
|
||||||
String shape) {
|
String shape) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record VisibleTopLevelSymbol(
|
||||||
|
String kind,
|
||||||
|
Span span) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,9 @@ public enum PbsSemanticsErrors {
|
|||||||
E_SEM_MALFORMED_RESERVED_ATTRIBUTE,
|
E_SEM_MALFORMED_RESERVED_ATTRIBUTE,
|
||||||
E_SEM_MISSING_CONST_TYPE_ANNOTATION,
|
E_SEM_MISSING_CONST_TYPE_ANNOTATION,
|
||||||
E_SEM_MISSING_CONST_INITIALIZER,
|
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_NON_CONSTANT_INITIALIZER,
|
||||||
E_SEM_CONST_INITIALIZER_TYPE_MISMATCH,
|
E_SEM_CONST_INITIALIZER_TYPE_MISMATCH,
|
||||||
E_SEM_CONST_CYCLIC_DEPENDENCY,
|
E_SEM_CONST_CYCLIC_DEPENDENCY,
|
||||||
|
|||||||
@ -144,6 +144,65 @@ class PbsSemanticsDeclarationsTest {
|
|||||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_DECLARATION.name())));
|
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
|
@Test
|
||||||
void shouldAllowSelfInStructServiceMethodsAndCtors() {
|
void shouldAllowSelfInStructServiceMethodsAndCtors() {
|
||||||
final var source = """
|
final var source = """
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user