implements PR007.1 clean up

This commit is contained in:
bQUARKz 2026-03-05 13:16:53 +00:00
parent 62a0405353
commit b4887afacb
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
7 changed files with 594 additions and 521 deletions

View File

@ -0,0 +1,33 @@
package p.studio.compiler.pbs.semantics;
import p.studio.compiler.source.identifiers.NameId;
record PbsCallableScope(
String label,
String scopeKind,
NameId ownerNameId) {
static PbsCallableScope topLevelFunctions() {
return new PbsCallableScope("top-level callable scope", "top-level", null);
}
static PbsCallableScope structMethods(final NameId ownerNameId) {
return new PbsCallableScope("struct method scope", "struct", ownerNameId);
}
static PbsCallableScope structCtors(final NameId ownerNameId) {
return new PbsCallableScope("struct constructor scope", "ctor", ownerNameId);
}
static PbsCallableScope serviceMethods(final NameId ownerNameId) {
return new PbsCallableScope("service method scope", "service", ownerNameId);
}
static PbsCallableScope contractSignatures(final NameId ownerNameId) {
return new PbsCallableScope("contract signature scope", "contract", ownerNameId);
}
static PbsCallableScope implementsMethods(final NameId ownerNameId) {
return new PbsCallableScope("implements method scope", "implements", ownerNameId);
}
}

View File

@ -0,0 +1,91 @@
package p.studio.compiler.pbs.semantics;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.utilities.structures.ReadOnlyList;
final class PbsCallableShape {
private PbsCallableShape() {
}
static String callableShapeKey(
final ReadOnlyList<PbsAst.Parameter> parameters,
final PbsAst.ReturnKind returnKind,
final PbsAst.TypeRef returnType,
final PbsAst.TypeRef resultErrorType) {
final var builder = new StringBuilder();
builder.append('(');
for (int i = 0; i < parameters.size(); i++) {
if (i > 0) {
builder.append(',');
}
builder.append(typeKey(parameters.get(i).typeRef()));
}
builder.append(")->");
builder.append(outputSurfaceKey(returnKind, returnType, resultErrorType));
return builder.toString();
}
static String ctorShapeKey(final ReadOnlyList<PbsAst.Parameter> parameters) {
final var builder = new StringBuilder();
builder.append('(');
for (int i = 0; i < parameters.size(); i++) {
if (i > 0) {
builder.append(',');
}
builder.append(typeKey(parameters.get(i).typeRef()));
}
builder.append(')');
return builder.toString();
}
private static String outputSurfaceKey(
final PbsAst.ReturnKind returnKind,
final PbsAst.TypeRef returnType,
final PbsAst.TypeRef resultErrorType) {
return switch (returnKind) {
case INFERRED_UNIT, EXPLICIT_UNIT -> "unit";
case PLAIN -> carrierTypeKey(returnType);
case RESULT -> "result<" + typeKey(resultErrorType) + ">" + carrierTypeKey(returnType);
};
}
private static String carrierTypeKey(final PbsAst.TypeRef typeRef) {
final var unwrapped = unwrapGroup(typeRef);
if (unwrapped != null && unwrapped.kind() == PbsAst.TypeRefKind.NAMED_TUPLE && unwrapped.fields().size() == 1) {
return typeKey(unwrapped.fields().getFirst().typeRef());
}
return typeKey(unwrapped);
}
private static PbsAst.TypeRef unwrapGroup(final PbsAst.TypeRef typeRef) {
if (typeRef == null) {
return null;
}
if (typeRef.kind() != PbsAst.TypeRefKind.GROUP) {
return typeRef;
}
return unwrapGroup(typeRef.inner());
}
private static String typeKey(final PbsAst.TypeRef typeRef) {
if (typeRef == null) {
return "unit";
}
final var unwrapped = unwrapGroup(typeRef);
if (unwrapped == null) {
return "unit";
}
return switch (unwrapped.kind()) {
case SIMPLE -> "simple:" + unwrapped.name();
case SELF -> "self";
case OPTIONAL -> "optional(" + typeKey(unwrapped.inner()) + ")";
case UNIT -> "unit";
case GROUP -> typeKey(unwrapped.inner());
case NAMED_TUPLE -> "tuple(" + unwrapped.fields().stream()
.map(field -> typeKey(field.typeRef()))
.reduce((left, right) -> left + "," + right)
.orElse("") + ")";
case ERROR -> "error";
};
}
}

View File

@ -0,0 +1,74 @@
package p.studio.compiler.pbs.semantics;
import p.studio.compiler.pbs.ast.PbsAst;
final class PbsCtorReturnScanner {
private PbsCtorReturnScanner() {
}
static boolean containsReturnStatement(final PbsAst.Block block) {
if (block == null) {
return false;
}
for (final var statement : block.statements()) {
if (statement instanceof PbsAst.ReturnStatement) {
return true;
}
if (statement instanceof PbsAst.IfStatement ifStatement) {
if (containsReturnInIfStatement(ifStatement)) {
return true;
}
continue;
}
if (statement instanceof PbsAst.ForStatement forStatement && containsReturnStatement(forStatement.body())) {
return true;
}
if (statement instanceof PbsAst.WhileStatement whileStatement && containsReturnStatement(whileStatement.body())) {
return true;
}
if (statement instanceof PbsAst.ExpressionStatement expressionStatement && containsReturnExpression(expressionStatement.expression())) {
return true;
}
}
return block.tailExpression() != null && containsReturnExpression(block.tailExpression());
}
private static boolean containsReturnInIfStatement(final PbsAst.IfStatement ifStatement) {
if (ifStatement == null) {
return false;
}
if (containsReturnStatement(ifStatement.thenBlock()) || containsReturnStatement(ifStatement.elseBlock())) {
return true;
}
return containsReturnInIfStatement(ifStatement.elseIf());
}
private static boolean containsReturnExpression(final PbsAst.Expression expression) {
if (expression == null) {
return false;
}
if (expression instanceof PbsAst.BlockExpr blockExpr) {
return containsReturnStatement(blockExpr.block());
}
if (expression instanceof PbsAst.IfExpr ifExpr) {
return containsReturnStatement(ifExpr.thenBlock()) || containsReturnExpression(ifExpr.elseExpression());
}
if (expression instanceof PbsAst.SwitchExpr switchExpr) {
for (final var arm : switchExpr.arms()) {
if (containsReturnStatement(arm.block())) {
return true;
}
}
}
if (expression instanceof PbsAst.HandleExpr handleExpr) {
for (final var arm : handleExpr.arms()) {
if (containsReturnStatement(arm.block())) {
return true;
}
}
}
return false;
}
}

View File

@ -0,0 +1,187 @@
package p.studio.compiler.pbs.semantics;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.diagnostics.RelatedSpan;
import p.studio.compiler.source.diagnostics.Severity;
import p.studio.compiler.source.identifiers.NameId;
import p.studio.compiler.source.tables.NameTable;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.HashMap;
import java.util.List;
final class PbsDeclarationRuleValidator {
private final NameTable nameTable;
private final DiagnosticSink diagnostics;
PbsDeclarationRuleValidator(
final NameTable nameTable,
final DiagnosticSink diagnostics) {
this.nameTable = nameTable;
this.diagnostics = diagnostics;
}
void validateCallableHeader(
final String callableName,
final ReadOnlyList<PbsAst.Parameter> parameters,
final PbsAst.ReturnKind returnKind,
final PbsAst.TypeRef returnType,
final PbsAst.TypeRef resultErrorType,
final Span span) {
validateParameters(parameters, "callable '%s'".formatted(callableName));
validateReturnSurface(returnKind, returnType, resultErrorType, "callable '%s'".formatted(callableName), span);
PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(
parameters.stream().map(PbsAst.Parameter::typeRef).toList(),
"callable '%s'".formatted(callableName),
diagnostics);
}
void validateParameters(
final ReadOnlyList<PbsAst.Parameter> parameters,
final String ownerDescription) {
final var seen = new HashMap<NameId, Span>();
for (final var parameter : parameters) {
final var parameterNameId = nameTable.register(parameter.name());
final var first = seen.putIfAbsent(parameterNameId, parameter.span());
if (first != null) {
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_PARAMETER_NAME.name(),
"Duplicate parameter name '%s' in %s".formatted(parameter.name(), ownerDescription),
parameter.span(),
List.of(new RelatedSpan("First parameter is here", first)));
}
}
}
void validateTypeSurfaces(
final List<PbsAst.TypeRef> typeRoots,
final String ownerDescription) {
PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(typeRoots, ownerDescription, diagnostics);
}
void validateErrorDeclaration(final PbsAst.ErrorDecl errorDecl) {
final var seenCases = new HashMap<NameId, Span>();
for (final var caseName : errorDecl.cases()) {
final var caseNameId = nameTable.register(caseName);
final var first = seenCases.putIfAbsent(caseNameId, errorDecl.span());
if (first != null) {
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_ERROR_CASE_LABEL.name(),
"Duplicate error case label '%s' in '%s'".formatted(caseName, errorDecl.name()),
errorDecl.span(),
List.of(new RelatedSpan("First case is here", first)));
}
}
}
void validateEnumDeclaration(final PbsAst.EnumDecl enumDecl) {
final var seenLabels = new HashMap<NameId, Span>();
final var seenIds = new HashMap<Long, Span>();
for (final var enumCase : enumDecl.cases()) {
final var labelId = nameTable.register(enumCase.name());
final var firstLabel = seenLabels.putIfAbsent(labelId, enumCase.span());
if (firstLabel != null) {
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_ENUM_CASE_LABEL.name(),
"Duplicate enum case label '%s' in '%s'".formatted(enumCase.name(), enumDecl.name()),
enumCase.span(),
List.of(new RelatedSpan("First case is here", firstLabel)));
}
if (enumCase.explicitValue() != null) {
final var firstId = seenIds.putIfAbsent(enumCase.explicitValue(), enumCase.span());
if (firstId != null) {
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_ENUM_CASE_ID.name(),
"Duplicate enum case id '%s' in '%s'".formatted(enumCase.explicitValue(), enumDecl.name()),
enumCase.span(),
List.of(new RelatedSpan("First id is here", firstId)));
}
}
}
}
void validateCallbackDeclaration(final PbsAst.CallbackDecl callbackDecl) {
validateParameters(callbackDecl.parameters(), "callback '%s'".formatted(callbackDecl.name()));
validateReturnSurface(
callbackDecl.returnKind(),
callbackDecl.returnType(),
callbackDecl.resultErrorType(),
"callback '%s'".formatted(callbackDecl.name()),
callbackDecl.span());
PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(
callbackDecl.parameters().stream().map(PbsAst.Parameter::typeRef).toList(),
"callback '%s'".formatted(callbackDecl.name()),
diagnostics);
}
void validateConstDeclaration(final PbsAst.ConstDecl constDecl) {
if (constDecl.explicitType() == null) {
diagnostics.error(
PbsSemanticsErrors.E_SEM_MISSING_CONST_TYPE_ANNOTATION.name(),
"Const declaration '%s' must include an explicit type annotation".formatted(constDecl.name()),
constDecl.span());
} else {
PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(
List.of(constDecl.explicitType()),
"const '%s'".formatted(constDecl.name()),
diagnostics);
}
if (constDecl.initializer() == null) {
diagnostics.error(
PbsSemanticsErrors.E_SEM_MISSING_CONST_INITIALIZER.name(),
"Non-builtin const declaration '%s' must include an initializer".formatted(constDecl.name()),
constDecl.span());
}
}
private void validateReturnSurface(
final PbsAst.ReturnKind returnKind,
final PbsAst.TypeRef returnType,
final PbsAst.TypeRef resultErrorType,
final String ownerDescription,
final Span span) {
if (returnKind == PbsAst.ReturnKind.RESULT && PbsTypeSurfaceSemanticsValidator.isOptionalType(returnType)) {
diagnostics.error(
PbsSemanticsErrors.E_SEM_INVALID_MIXED_OPTIONAL_RESULT_RETURN.name(),
"Return surface for %s cannot combine 'result' with top-level 'optional' payload".formatted(ownerDescription),
span);
}
if (returnType != null && returnType.kind() == PbsAst.TypeRefKind.NAMED_TUPLE) {
validateDuplicateOutputLabels(returnType, ownerDescription);
}
if (returnType != null) {
PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(List.of(returnType), ownerDescription, diagnostics);
}
if (resultErrorType != null) {
PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(List.of(resultErrorType), ownerDescription, diagnostics);
}
}
private void validateDuplicateOutputLabels(
final PbsAst.TypeRef namedTupleReturnType,
final String ownerDescription) {
final var seen = new HashMap<NameId, Span>();
for (final var field : namedTupleReturnType.fields()) {
final var labelId = nameTable.register(field.label());
final var first = seen.putIfAbsent(labelId, field.span());
if (first != null) {
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_RETURN_LABEL.name(),
"Duplicate return label '%s' in %s".formatted(field.label(), ownerDescription),
field.span(),
List.of(new RelatedSpan("First label is here", first)));
}
}
}
}

View File

@ -3,27 +3,20 @@ package p.studio.compiler.pbs.semantics;
import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.source.Span; import p.studio.compiler.source.Span;
import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.diagnostics.RelatedSpan;
import p.studio.compiler.source.diagnostics.Severity;
import p.studio.compiler.source.identifiers.NameId;
import p.studio.compiler.source.tables.NameTable; import p.studio.compiler.source.tables.NameTable;
import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class PbsDeclarationSemanticsValidator { public final class PbsDeclarationSemanticsValidator {
private final NameTable nameTable = new NameTable(); private final NameTable nameTable = new NameTable();
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) { public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
final var binder = new NamespaceBinder(nameTable, diagnostics); final var binder = new PbsNamespaceBinder(nameTable, diagnostics);
final var rules = new PbsDeclarationRuleValidator(nameTable, diagnostics);
for (final var topDecl : ast.topDecls()) { for (final var topDecl : ast.topDecls()) {
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) { if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
validateCallable( validateCallable(
CallableScope.topLevelFunctions(), PbsCallableScope.topLevelFunctions(),
functionDecl.name(), functionDecl.name(),
functionDecl.parameters(), functionDecl.parameters(),
functionDecl.returnKind(), functionDecl.returnKind(),
@ -31,64 +24,66 @@ public final class PbsDeclarationSemanticsValidator {
functionDecl.resultErrorType(), functionDecl.resultErrorType(),
functionDecl.span(), functionDecl.span(),
binder, binder,
diagnostics); rules);
continue; continue;
} }
if (topDecl instanceof PbsAst.StructDecl structDecl) { if (topDecl instanceof PbsAst.StructDecl structDecl) {
binder.registerType(structDecl.name(), structDecl.span(), "struct"); binder.registerType(structDecl.name(), structDecl.span(), "struct");
validateStructDeclaration(structDecl, binder, diagnostics); validateStructDeclaration(structDecl, binder, rules, diagnostics);
continue; continue;
} }
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");
validateServiceDeclaration(serviceDecl, binder, diagnostics); validateServiceDeclaration(serviceDecl, binder, rules);
continue; continue;
} }
if (topDecl instanceof PbsAst.ContractDecl contractDecl) { if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
binder.registerType(contractDecl.name(), contractDecl.span(), "contract"); binder.registerType(contractDecl.name(), contractDecl.span(), "contract");
validateContractDeclaration(contractDecl, binder, diagnostics); validateContractDeclaration(contractDecl, binder, rules);
continue; continue;
} }
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) { if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
binder.registerType(errorDecl.name(), errorDecl.span(), "error"); binder.registerType(errorDecl.name(), errorDecl.span(), "error");
validateErrorDeclaration(errorDecl, diagnostics); rules.validateErrorDeclaration(errorDecl);
continue; continue;
} }
if (topDecl instanceof PbsAst.EnumDecl enumDecl) { if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
binder.registerType(enumDecl.name(), enumDecl.span(), "enum"); binder.registerType(enumDecl.name(), enumDecl.span(), "enum");
validateEnumDeclaration(enumDecl, diagnostics); rules.validateEnumDeclaration(enumDecl);
continue; continue;
} }
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) { if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
binder.registerType(callbackDecl.name(), callbackDecl.span(), "callback"); binder.registerType(callbackDecl.name(), callbackDecl.span(), "callback");
validateCallbackDeclaration(callbackDecl, diagnostics); rules.validateCallbackDeclaration(callbackDecl);
continue; 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");
validateConstDeclaration(constDecl, diagnostics); rules.validateConstDeclaration(constDecl);
continue; continue;
} }
if (topDecl instanceof PbsAst.ImplementsDecl implementsDecl) { if (topDecl instanceof PbsAst.ImplementsDecl implementsDecl) {
validateImplementsDeclaration(implementsDecl, binder, diagnostics); validateImplementsDeclaration(implementsDecl, binder, rules);
} }
} }
} }
private void validateStructDeclaration( private void validateStructDeclaration(
final PbsAst.StructDecl structDecl, final PbsAst.StructDecl structDecl,
final NamespaceBinder binder, final PbsNamespaceBinder binder,
final PbsDeclarationRuleValidator rules,
final DiagnosticSink diagnostics) { final DiagnosticSink diagnostics) {
final var methodScope = CallableScope.structMethods(nameTable.register(structDecl.name())); final var ownerNameId = nameTable.register(structDecl.name());
final var methodScope = PbsCallableScope.structMethods(ownerNameId);
for (final var method : structDecl.methods()) { for (final var method : structDecl.methods()) {
validateCallable( validateCallable(
methodScope, methodScope,
@ -99,19 +94,18 @@ public final class PbsDeclarationSemanticsValidator {
method.resultErrorType(), method.resultErrorType(),
method.span(), method.span(),
binder, binder,
diagnostics); rules);
} }
final var ctorScope = CallableScope.structCtors(nameTable.register(structDecl.name())); final var ctorScope = PbsCallableScope.structCtors(ownerNameId);
for (final var ctor : structDecl.ctors()) { for (final var ctor : structDecl.ctors()) {
validateParameters(ctor.parameters(), "ctor '%s'".formatted(ctor.name()), diagnostics); rules.validateParameters(ctor.parameters(), "ctor '%s'".formatted(ctor.name()));
validateTypeSurfaceRecursive( rules.validateTypeSurfaces(
ctor.parameters().stream().map(PbsAst.Parameter::typeRef).toList(), ctor.parameters().stream().map(PbsAst.Parameter::typeRef).toList(),
"ctor '%s'".formatted(ctor.name()), "ctor '%s'".formatted(ctor.name()));
diagnostics);
binder.registerCtor(ctorScope, ctor.name(), ctorShapeKey(ctor.parameters()), ctor.span()); binder.registerCtor(ctorScope, ctor.name(), PbsCallableShape.ctorShapeKey(ctor.parameters()), ctor.span());
if (containsReturnStatement(ctor.body())) { if (PbsCtorReturnScanner.containsReturnStatement(ctor.body())) {
diagnostics.error( diagnostics.error(
PbsSemanticsErrors.E_SEM_INVALID_RETURN_INSIDE_CTOR.name(), PbsSemanticsErrors.E_SEM_INVALID_RETURN_INSIDE_CTOR.name(),
"Constructors cannot contain 'return' statements", "Constructors cannot contain 'return' statements",
@ -122,9 +116,9 @@ public final class PbsDeclarationSemanticsValidator {
private void validateServiceDeclaration( private void validateServiceDeclaration(
final PbsAst.ServiceDecl serviceDecl, final PbsAst.ServiceDecl serviceDecl,
final NamespaceBinder binder, final PbsNamespaceBinder binder,
final DiagnosticSink diagnostics) { final PbsDeclarationRuleValidator rules) {
final var methodScope = CallableScope.serviceMethods(nameTable.register(serviceDecl.name())); final var methodScope = PbsCallableScope.serviceMethods(nameTable.register(serviceDecl.name()));
for (final var method : serviceDecl.methods()) { for (final var method : serviceDecl.methods()) {
validateCallable( validateCallable(
methodScope, methodScope,
@ -135,15 +129,15 @@ public final class PbsDeclarationSemanticsValidator {
method.resultErrorType(), method.resultErrorType(),
method.span(), method.span(),
binder, binder,
diagnostics); rules);
} }
} }
private void validateContractDeclaration( private void validateContractDeclaration(
final PbsAst.ContractDecl contractDecl, final PbsAst.ContractDecl contractDecl,
final NamespaceBinder binder, final PbsNamespaceBinder binder,
final DiagnosticSink diagnostics) { final PbsDeclarationRuleValidator rules) {
final var signatureScope = CallableScope.contractSignatures(nameTable.register(contractDecl.name())); final var signatureScope = PbsCallableScope.contractSignatures(nameTable.register(contractDecl.name()));
for (final var signature : contractDecl.signatures()) { for (final var signature : contractDecl.signatures()) {
validateCallable( validateCallable(
signatureScope, signatureScope,
@ -154,105 +148,16 @@ public final class PbsDeclarationSemanticsValidator {
signature.resultErrorType(), signature.resultErrorType(),
signature.span(), signature.span(),
binder, binder,
diagnostics); rules);
}
}
private void validateErrorDeclaration(
final PbsAst.ErrorDecl errorDecl,
final DiagnosticSink diagnostics) {
final var seenCases = new HashMap<NameId, Span>();
for (final var caseName : errorDecl.cases()) {
final var caseNameId = nameTable.register(caseName);
final var first = seenCases.putIfAbsent(caseNameId, errorDecl.span());
if (first != null) {
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_ERROR_CASE_LABEL.name(),
"Duplicate error case label '%s' in '%s'".formatted(caseName, errorDecl.name()),
errorDecl.span(),
List.of(new RelatedSpan("First case is here", first)));
}
}
}
private void validateEnumDeclaration(
final PbsAst.EnumDecl enumDecl,
final DiagnosticSink diagnostics) {
final var seenLabels = new HashMap<NameId, Span>();
final var seenIds = new HashMap<Long, Span>();
for (final var enumCase : enumDecl.cases()) {
final var labelId = nameTable.register(enumCase.name());
final var firstLabel = seenLabels.putIfAbsent(labelId, enumCase.span());
if (firstLabel != null) {
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_ENUM_CASE_LABEL.name(),
"Duplicate enum case label '%s' in '%s'".formatted(enumCase.name(), enumDecl.name()),
enumCase.span(),
List.of(new RelatedSpan("First case is here", firstLabel)));
}
if (enumCase.explicitValue() != null) {
final var firstId = seenIds.putIfAbsent(enumCase.explicitValue(), enumCase.span());
if (firstId != null) {
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_ENUM_CASE_ID.name(),
"Duplicate enum case id '%s' in '%s'".formatted(enumCase.explicitValue(), enumDecl.name()),
enumCase.span(),
List.of(new RelatedSpan("First id is here", firstId)));
}
}
}
}
private void validateCallbackDeclaration(
final PbsAst.CallbackDecl callbackDecl,
final DiagnosticSink diagnostics) {
validateParameters(callbackDecl.parameters(), "callback '%s'".formatted(callbackDecl.name()), diagnostics);
validateReturnSurface(
callbackDecl.returnKind(),
callbackDecl.returnType(),
callbackDecl.resultErrorType(),
"callback '%s'".formatted(callbackDecl.name()),
callbackDecl.span(),
diagnostics);
validateTypeSurfaceRecursive(
callbackDecl.parameters().stream().map(PbsAst.Parameter::typeRef).toList(),
"callback '%s'".formatted(callbackDecl.name()),
diagnostics);
}
private void validateConstDeclaration(
final PbsAst.ConstDecl constDecl,
final DiagnosticSink diagnostics) {
if (constDecl.explicitType() == null) {
diagnostics.error(
PbsSemanticsErrors.E_SEM_MISSING_CONST_TYPE_ANNOTATION.name(),
"Const declaration '%s' must include an explicit type annotation".formatted(constDecl.name()),
constDecl.span());
} else {
validateTypeSurfaceRecursive(
List.of(constDecl.explicitType()),
"const '%s'".formatted(constDecl.name()),
diagnostics);
}
if (constDecl.initializer() == null) {
diagnostics.error(
PbsSemanticsErrors.E_SEM_MISSING_CONST_INITIALIZER.name(),
"Non-builtin const declaration '%s' must include an initializer".formatted(constDecl.name()),
constDecl.span());
} }
} }
private void validateImplementsDeclaration( private void validateImplementsDeclaration(
final PbsAst.ImplementsDecl implementsDecl, final PbsAst.ImplementsDecl implementsDecl,
final NamespaceBinder binder, final PbsNamespaceBinder binder,
final DiagnosticSink diagnostics) { final PbsDeclarationRuleValidator rules) {
final var ownerNameId = nameTable.register(implementsDecl.ownerName() + ":" + implementsDecl.contractName()); final var ownerNameId = nameTable.register(implementsDecl.ownerName() + ":" + implementsDecl.contractName());
final var scope = CallableScope.implementsMethods(ownerNameId); final var scope = PbsCallableScope.implementsMethods(ownerNameId);
for (final var method : implementsDecl.methods()) { for (final var method : implementsDecl.methods()) {
validateCallable( validateCallable(
scope, scope,
@ -263,407 +168,25 @@ public final class PbsDeclarationSemanticsValidator {
method.resultErrorType(), method.resultErrorType(),
method.span(), method.span(),
binder, binder,
diagnostics); rules);
} }
} }
private void validateCallable( private void validateCallable(
final CallableScope scope, final PbsCallableScope scope,
final String callableName, final String callableName,
final ReadOnlyList<PbsAst.Parameter> parameters, final ReadOnlyList<PbsAst.Parameter> parameters,
final PbsAst.ReturnKind returnKind, final PbsAst.ReturnKind returnKind,
final PbsAst.TypeRef returnType, final PbsAst.TypeRef returnType,
final PbsAst.TypeRef resultErrorType, final PbsAst.TypeRef resultErrorType,
final Span span, final Span span,
final NamespaceBinder binder, final PbsNamespaceBinder binder,
final DiagnosticSink diagnostics) { final PbsDeclarationRuleValidator rules) {
validateParameters(parameters, "callable '%s'".formatted(callableName), diagnostics); rules.validateCallableHeader(callableName, parameters, returnKind, returnType, resultErrorType, span);
validateReturnSurface(returnKind, returnType, resultErrorType, "callable '%s'".formatted(callableName), span, diagnostics); binder.registerCallable(
validateTypeSurfaceRecursive(parameters.stream().map(PbsAst.Parameter::typeRef).toList(), "callable '%s'".formatted(callableName), diagnostics); scope,
callableName,
binder.registerCallable(scope, callableName, callableShapeKey(parameters, returnKind, returnType, resultErrorType), span); PbsCallableShape.callableShapeKey(parameters, returnKind, returnType, resultErrorType),
} span);
private void validateParameters(
final ReadOnlyList<PbsAst.Parameter> parameters,
final String ownerDescription,
final DiagnosticSink diagnostics) {
final var seen = new HashMap<NameId, Span>();
for (final var parameter : parameters) {
final var parameterNameId = nameTable.register(parameter.name());
final var first = seen.putIfAbsent(parameterNameId, parameter.span());
if (first != null) {
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_PARAMETER_NAME.name(),
"Duplicate parameter name '%s' in %s".formatted(parameter.name(), ownerDescription),
parameter.span(),
List.of(new RelatedSpan("First parameter is here", first)));
}
}
}
private void validateReturnSurface(
final PbsAst.ReturnKind returnKind,
final PbsAst.TypeRef returnType,
final PbsAst.TypeRef resultErrorType,
final String ownerDescription,
final Span span,
final DiagnosticSink diagnostics) {
if (returnKind == PbsAst.ReturnKind.RESULT && isOptionalType(returnType)) {
diagnostics.error(
PbsSemanticsErrors.E_SEM_INVALID_MIXED_OPTIONAL_RESULT_RETURN.name(),
"Return surface for %s cannot combine 'result' with top-level 'optional' payload".formatted(ownerDescription),
span);
}
if (returnType != null && returnType.kind() == PbsAst.TypeRefKind.NAMED_TUPLE) {
validateDuplicateOutputLabels(returnType, ownerDescription, diagnostics);
}
if (returnType != null) {
validateTypeSurfaceRecursive(List.of(returnType), ownerDescription, diagnostics);
}
if (resultErrorType != null) {
validateTypeSurfaceRecursive(List.of(resultErrorType), ownerDescription, diagnostics);
}
}
private void validateDuplicateOutputLabels(
final PbsAst.TypeRef namedTupleReturnType,
final String ownerDescription,
final DiagnosticSink diagnostics) {
final var seen = new HashMap<NameId, Span>();
for (final var field : namedTupleReturnType.fields()) {
final var labelId = nameTable.register(field.label());
final var first = seen.putIfAbsent(labelId, field.span());
if (first != null) {
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_RETURN_LABEL.name(),
"Duplicate return label '%s' in %s".formatted(field.label(), ownerDescription),
field.span(),
List.of(new RelatedSpan("First label is here", first)));
}
}
}
private void validateTypeSurfaceRecursive(
final List<PbsAst.TypeRef> roots,
final String ownerDescription,
final DiagnosticSink diagnostics) {
final var pending = new ArrayList<>(roots);
while (!pending.isEmpty()) {
final var typeRef = pending.removeLast();
if (typeRef == null) {
continue;
}
switch (typeRef.kind()) {
case OPTIONAL -> {
if (typeRef.inner() == null || typeRef.inner().kind() == PbsAst.TypeRefKind.UNIT) {
diagnostics.error(
PbsSemanticsErrors.E_SEM_INVALID_OPTIONAL_VOID_TYPE_SURFACE.name(),
"Invalid optional type surface in %s: 'optional void' is not allowed".formatted(ownerDescription),
typeRef.span());
}
pending.add(typeRef.inner());
}
case GROUP -> pending.add(typeRef.inner());
case NAMED_TUPLE -> {
for (final var field : typeRef.fields()) {
pending.add(field.typeRef());
}
}
default -> {
// No recursive children.
}
}
}
}
private boolean isOptionalType(final PbsAst.TypeRef typeRef) {
if (typeRef == null) {
return false;
}
if (typeRef.kind() == PbsAst.TypeRefKind.OPTIONAL) {
return true;
}
if (typeRef.kind() == PbsAst.TypeRefKind.GROUP) {
return isOptionalType(typeRef.inner());
}
return false;
}
private boolean containsReturnStatement(final PbsAst.Block block) {
if (block == null) {
return false;
}
for (final var statement : block.statements()) {
if (statement instanceof PbsAst.ReturnStatement) {
return true;
}
if (statement instanceof PbsAst.IfStatement ifStatement) {
if (containsReturnInIfStatement(ifStatement)) {
return true;
}
continue;
}
if (statement instanceof PbsAst.ForStatement forStatement && containsReturnStatement(forStatement.body())) {
return true;
}
if (statement instanceof PbsAst.WhileStatement whileStatement && containsReturnStatement(whileStatement.body())) {
return true;
}
if (statement instanceof PbsAst.ExpressionStatement expressionStatement && containsReturnExpression(expressionStatement.expression())) {
return true;
}
}
return block.tailExpression() != null && containsReturnExpression(block.tailExpression());
}
private boolean containsReturnInIfStatement(final PbsAst.IfStatement ifStatement) {
if (ifStatement == null) {
return false;
}
if (containsReturnStatement(ifStatement.thenBlock()) || containsReturnStatement(ifStatement.elseBlock())) {
return true;
}
return containsReturnInIfStatement(ifStatement.elseIf());
}
private boolean containsReturnExpression(final PbsAst.Expression expression) {
if (expression == null) {
return false;
}
if (expression instanceof PbsAst.BlockExpr blockExpr) {
return containsReturnStatement(blockExpr.block());
}
if (expression instanceof PbsAst.IfExpr ifExpr) {
return containsReturnStatement(ifExpr.thenBlock()) || containsReturnExpression(ifExpr.elseExpression());
}
if (expression instanceof PbsAst.SwitchExpr switchExpr) {
for (final var arm : switchExpr.arms()) {
if (containsReturnStatement(arm.block())) {
return true;
}
}
}
if (expression instanceof PbsAst.HandleExpr handleExpr) {
for (final var arm : handleExpr.arms()) {
if (containsReturnStatement(arm.block())) {
return true;
}
}
}
return false;
}
private String callableShapeKey(
final ReadOnlyList<PbsAst.Parameter> parameters,
final PbsAst.ReturnKind returnKind,
final PbsAst.TypeRef returnType,
final PbsAst.TypeRef resultErrorType) {
final var builder = new StringBuilder();
builder.append('(');
for (int i = 0; i < parameters.size(); i++) {
if (i > 0) {
builder.append(',');
}
builder.append(typeKey(parameters.get(i).typeRef()));
}
builder.append(")->");
builder.append(outputSurfaceKey(returnKind, returnType, resultErrorType));
return builder.toString();
}
private String ctorShapeKey(final ReadOnlyList<PbsAst.Parameter> parameters) {
final var builder = new StringBuilder();
builder.append('(');
for (int i = 0; i < parameters.size(); i++) {
if (i > 0) {
builder.append(',');
}
builder.append(typeKey(parameters.get(i).typeRef()));
}
builder.append(')');
return builder.toString();
}
private String outputSurfaceKey(
final PbsAst.ReturnKind returnKind,
final PbsAst.TypeRef returnType,
final PbsAst.TypeRef resultErrorType) {
return switch (returnKind) {
case INFERRED_UNIT, EXPLICIT_UNIT -> "unit";
case PLAIN -> carrierTypeKey(returnType);
case RESULT -> "result<" + typeKey(resultErrorType) + ">" + carrierTypeKey(returnType);
};
}
private String carrierTypeKey(final PbsAst.TypeRef typeRef) {
final var unwrapped = unwrapGroup(typeRef);
if (unwrapped != null && unwrapped.kind() == PbsAst.TypeRefKind.NAMED_TUPLE && unwrapped.fields().size() == 1) {
return typeKey(unwrapped.fields().getFirst().typeRef());
}
return typeKey(unwrapped);
}
private PbsAst.TypeRef unwrapGroup(final PbsAst.TypeRef typeRef) {
if (typeRef == null) {
return null;
}
if (typeRef.kind() != PbsAst.TypeRefKind.GROUP) {
return typeRef;
}
return unwrapGroup(typeRef.inner());
}
private String typeKey(final PbsAst.TypeRef typeRef) {
if (typeRef == null) {
return "unit";
}
final var unwrapped = unwrapGroup(typeRef);
if (unwrapped == null) {
return "unit";
}
return switch (unwrapped.kind()) {
case SIMPLE -> "simple:" + unwrapped.name();
case SELF -> "self";
case OPTIONAL -> "optional(" + typeKey(unwrapped.inner()) + ")";
case UNIT -> "unit";
case GROUP -> typeKey(unwrapped.inner());
case NAMED_TUPLE -> "tuple(" + unwrapped.fields().stream()
.map(field -> typeKey(field.typeRef()))
.reduce((left, right) -> left + "," + right)
.orElse("") + ")";
case ERROR -> "error";
};
}
private static final class NamespaceBinder {
private final NameTable nameTable;
private final DiagnosticSink diagnostics;
private final Map<NameId, Span> typeNamespace = new HashMap<>();
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 NamespaceBinder(final NameTable nameTable, final DiagnosticSink diagnostics) {
this.nameTable = nameTable;
this.diagnostics = diagnostics;
}
private void registerType(final String name, final Span span, final String kind) {
registerByNamespace(typeNamespace, name, span, "type namespace", kind);
}
private void registerValue(final String name, final Span span, final String kind) {
registerByNamespace(valueNamespace, name, span, "value namespace", kind);
}
@SuppressWarnings("unused")
private void registerHostOwner(final String name, final Span span, final String kind) {
registerByNamespace(hostOwnerNamespace, name, span, "host-owner namespace", kind);
}
private void registerCallable(
final CallableScope scope,
final String callableName,
final String shape,
final Span span) {
final var callableNameId = nameTable.register(callableName);
final var identity = new CallableIdentity(scope, callableNameId, shape);
final var first = callableNamespace.putIfAbsent(identity, span);
if (first == null) {
return;
}
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name(),
"Duplicate callable declaration '%s' with shape %s in %s".formatted(callableName, shape, scope.label()),
span,
List.of(new RelatedSpan("First callable declaration is here", first)));
}
private void registerCtor(
final CallableScope scope,
final String ctorName,
final String shape,
final Span span) {
final var callableNameId = nameTable.register(ctorName);
final var identity = new CallableIdentity(scope, callableNameId, shape);
final var first = callableNamespace.putIfAbsent(identity, span);
if (first == null) {
return;
}
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name(),
"Duplicate constructor declaration '%s' with shape %s in %s".formatted(ctorName, shape, scope.label()),
span,
List.of(new RelatedSpan("First constructor declaration is here", first)));
}
private void registerByNamespace(
final Map<NameId, Span> namespace,
final String declarationName,
final Span span,
final String namespaceName,
final String declarationKind) {
final var nameId = nameTable.register(declarationName);
final var first = namespace.putIfAbsent(nameId, span);
if (first == null) {
return;
}
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_DECLARATION.name(),
"Duplicate %s declaration '%s' in %s".formatted(declarationKind, declarationName, namespaceName),
span,
List.of(new RelatedSpan("First declaration is here", first)));
}
}
private record CallableIdentity(
CallableScope scope,
NameId callableNameId,
String shape) {
}
private record CallableScope(
String label,
String scopeKind,
NameId ownerNameId) {
private static CallableScope topLevelFunctions() {
return new CallableScope("top-level callable scope", "top-level", null);
}
private static CallableScope structMethods(final NameId ownerNameId) {
return new CallableScope("struct method scope", "struct", ownerNameId);
}
private static CallableScope structCtors(final NameId ownerNameId) {
return new CallableScope("struct constructor scope", "ctor", ownerNameId);
}
private static CallableScope serviceMethods(final NameId ownerNameId) {
return new CallableScope("service method scope", "service", ownerNameId);
}
private static CallableScope contractSignatures(final NameId ownerNameId) {
return new CallableScope("contract signature scope", "contract", ownerNameId);
}
private static CallableScope implementsMethods(final NameId ownerNameId) {
return new CallableScope("implements method scope", "implements", ownerNameId);
}
} }
} }

View File

@ -0,0 +1,106 @@
package p.studio.compiler.pbs.semantics;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.diagnostics.RelatedSpan;
import p.studio.compiler.source.diagnostics.Severity;
import p.studio.compiler.source.identifiers.NameId;
import p.studio.compiler.source.tables.NameTable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
final class PbsNamespaceBinder {
private final NameTable nameTable;
private final DiagnosticSink diagnostics;
private final Map<NameId, Span> typeNamespace = new HashMap<>();
private final Map<NameId, Span> valueNamespace = new HashMap<>();
private final Map<NameId, Span> hostOwnerNamespace = new HashMap<>();
private final Map<CallableIdentity, Span> callableNamespace = new HashMap<>();
PbsNamespaceBinder(final NameTable nameTable, final DiagnosticSink diagnostics) {
this.nameTable = nameTable;
this.diagnostics = diagnostics;
}
void registerType(final String name, final Span span, final String kind) {
registerByNamespace(typeNamespace, name, span, "type namespace", kind);
}
void registerValue(final String name, final Span span, final String kind) {
registerByNamespace(valueNamespace, name, span, "value namespace", kind);
}
@SuppressWarnings("unused")
void registerHostOwner(final String name, final Span span, final String kind) {
registerByNamespace(hostOwnerNamespace, name, span, "host-owner namespace", kind);
}
void registerCallable(
final PbsCallableScope scope,
final String callableName,
final String shape,
final Span span) {
final var callableNameId = nameTable.register(callableName);
final var identity = new CallableIdentity(scope, callableNameId, shape);
final var first = callableNamespace.putIfAbsent(identity, span);
if (first == null) {
return;
}
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name(),
"Duplicate callable declaration '%s' with shape %s in %s".formatted(callableName, shape, scope.label()),
span,
List.of(new RelatedSpan("First callable declaration is here", first)));
}
void registerCtor(
final PbsCallableScope scope,
final String ctorName,
final String shape,
final Span span) {
final var callableNameId = nameTable.register(ctorName);
final var identity = new CallableIdentity(scope, callableNameId, shape);
final var first = callableNamespace.putIfAbsent(identity, span);
if (first == null) {
return;
}
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name(),
"Duplicate constructor declaration '%s' with shape %s in %s".formatted(ctorName, shape, scope.label()),
span,
List.of(new RelatedSpan("First constructor declaration is here", first)));
}
private void registerByNamespace(
final Map<NameId, Span> namespace,
final String declarationName,
final Span span,
final String namespaceName,
final String declarationKind) {
final var nameId = nameTable.register(declarationName);
final var first = namespace.putIfAbsent(nameId, span);
if (first == null) {
return;
}
diagnostics.report(
Severity.Error,
PbsSemanticsErrors.E_SEM_DUPLICATE_DECLARATION.name(),
"Duplicate %s declaration '%s' in %s".formatted(declarationKind, declarationName, namespaceName),
span,
List.of(new RelatedSpan("First declaration is here", first)));
}
private record CallableIdentity(
PbsCallableScope scope,
NameId callableNameId,
String shape) {
}
}

View File

@ -0,0 +1,59 @@
package p.studio.compiler.pbs.semantics;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import java.util.ArrayList;
import java.util.List;
final class PbsTypeSurfaceSemanticsValidator {
private PbsTypeSurfaceSemanticsValidator() {
}
static void validateTypeSurfaceRecursive(
final List<PbsAst.TypeRef> roots,
final String ownerDescription,
final DiagnosticSink diagnostics) {
final var pending = new ArrayList<>(roots);
while (!pending.isEmpty()) {
final var typeRef = pending.removeLast();
if (typeRef == null) {
continue;
}
switch (typeRef.kind()) {
case OPTIONAL -> {
if (typeRef.inner() == null || typeRef.inner().kind() == PbsAst.TypeRefKind.UNIT) {
diagnostics.error(
PbsSemanticsErrors.E_SEM_INVALID_OPTIONAL_VOID_TYPE_SURFACE.name(),
"Invalid optional type surface in %s: 'optional void' is not allowed".formatted(ownerDescription),
typeRef.span());
}
pending.add(typeRef.inner());
}
case GROUP -> pending.add(typeRef.inner());
case NAMED_TUPLE -> {
for (final var field : typeRef.fields()) {
pending.add(field.typeRef());
}
}
default -> {
// No recursive children.
}
}
}
}
static boolean isOptionalType(final PbsAst.TypeRef typeRef) {
if (typeRef == null) {
return false;
}
if (typeRef.kind() == PbsAst.TypeRefKind.OPTIONAL) {
return true;
}
if (typeRef.kind() == PbsAst.TypeRefKind.GROUP) {
return isOptionalType(typeRef.inner());
}
return false;
}
}