implements PR007.1 clean up
This commit is contained in:
parent
62a0405353
commit
b4887afacb
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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),
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user