implements PR-19.6 lifecycle and initallowed semantics
This commit is contained in:
parent
2003fc749e
commit
7fc38010a2
@ -11,6 +11,7 @@ import p.studio.compiler.pbs.lowering.PbsExecutableLoweringService;
|
||||
import p.studio.compiler.pbs.parser.PbsParser;
|
||||
import p.studio.compiler.pbs.semantics.PbsDeclarationSemanticsValidator;
|
||||
import p.studio.compiler.pbs.semantics.PbsFlowSemanticsValidator;
|
||||
import p.studio.compiler.pbs.semantics.PbsLifecycleSemanticsValidator;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.identifiers.FileId;
|
||||
import p.studio.compiler.source.identifiers.ModuleId;
|
||||
@ -23,6 +24,7 @@ import java.util.HashSet;
|
||||
|
||||
public final class PbsFrontendCompiler {
|
||||
private final PbsFlowSemanticsValidator flowSemanticsValidator = new PbsFlowSemanticsValidator();
|
||||
private final PbsLifecycleSemanticsValidator lifecycleSemanticsValidator = new PbsLifecycleSemanticsValidator();
|
||||
private final PbsReservedMetadataExtractor reservedMetadataExtractor = new PbsReservedMetadataExtractor();
|
||||
private final PbsHostAdmissionValidator hostAdmissionValidator = new PbsHostAdmissionValidator();
|
||||
private final PbsExecutableLoweringService executableLoweringService = new PbsExecutableLoweringService();
|
||||
@ -130,6 +132,7 @@ public final class PbsFrontendCompiler {
|
||||
effectiveImportedGlobals,
|
||||
diagnostics);
|
||||
flowSemanticsValidator.validate(ast, effectiveSupplementalTopDecls, diagnostics);
|
||||
lifecycleSemanticsValidator.validate(ast, effectiveSupplementalTopDecls, diagnostics);
|
||||
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
||||
return IRBackendFile.empty(fileId);
|
||||
}
|
||||
|
||||
@ -17,13 +17,15 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
||||
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
||||
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
||||
private static final String ATTR_INIT_ALLOWED = "InitAllowed";
|
||||
|
||||
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
|
||||
ATTR_HOST,
|
||||
ATTR_CAPABILITY,
|
||||
ATTR_BUILTIN_TYPE,
|
||||
ATTR_BUILTIN_CONST,
|
||||
ATTR_INTRINSIC_CALL);
|
||||
ATTR_INTRINSIC_CALL,
|
||||
ATTR_INIT_ALLOWED);
|
||||
|
||||
private final NameTable nameTable;
|
||||
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
||||
@ -400,11 +402,14 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
final PbsAst.FunctionSignature signature,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var hostAttributes = attributesNamed(signature.attributes(), ATTR_HOST);
|
||||
final var initAllowedAttributes = attributesNamed(signature.attributes(), ATTR_INIT_ALLOWED);
|
||||
for (final var attribute : signature.attributes()) {
|
||||
if (!isReservedAttribute(attribute.name())) {
|
||||
continue;
|
||||
}
|
||||
if (ATTR_HOST.equals(attribute.name()) || ATTR_CAPABILITY.equals(attribute.name())) {
|
||||
if (ATTR_HOST.equals(attribute.name())
|
||||
|| ATTR_CAPABILITY.equals(attribute.name())
|
||||
|| ATTR_INIT_ALLOWED.equals(attribute.name())) {
|
||||
continue;
|
||||
}
|
||||
reportInvalidReservedAttributeTarget(
|
||||
@ -427,7 +432,16 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
}
|
||||
}
|
||||
|
||||
if (initAllowedAttributes.size() > 1) {
|
||||
for (int i = 1; i < initAllowedAttributes.size(); i++) {
|
||||
reportDuplicateReservedAttribute(initAllowedAttributes.get(i), ATTR_INIT_ALLOWED, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
validateHostAttributeShape(hostAttributes.getFirst(), diagnostics);
|
||||
if (!initAllowedAttributes.isEmpty()) {
|
||||
validateInitAllowedAttributeShape(initAllowedAttributes.getFirst(), diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateBuiltinTypeAttribute(
|
||||
@ -558,6 +572,21 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateInitAllowedAttributeShape(
|
||||
final PbsAst.Attribute attribute,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var args = validateNamedArguments(attribute, Set.of(), Set.of(), diagnostics);
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
if (!args.isEmpty()) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||
"InitAllowed does not accept arguments",
|
||||
attribute.span());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, PbsAst.AttributeValue> validateNamedArguments(
|
||||
final PbsAst.Attribute attribute,
|
||||
final Set<String> requiredNames,
|
||||
|
||||
@ -0,0 +1,274 @@
|
||||
package p.studio.compiler.pbs.semantics;
|
||||
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public final class PbsLifecycleSemanticsValidator {
|
||||
private static final String ATTR_INIT_ALLOWED = "InitAllowed";
|
||||
|
||||
public void validate(
|
||||
final PbsAst.File ast,
|
||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||
final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics) {
|
||||
final var initFunctions = new ArrayList<PbsAst.FunctionDecl>();
|
||||
for (final var topDecl : ast.topDecls()) {
|
||||
if (!(topDecl instanceof PbsAst.FunctionDecl functionDecl)) {
|
||||
continue;
|
||||
}
|
||||
if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.NONE) {
|
||||
continue;
|
||||
}
|
||||
validateLifecycleSignature(functionDecl, diagnostics);
|
||||
if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.INIT) {
|
||||
initFunctions.add(functionDecl);
|
||||
}
|
||||
}
|
||||
|
||||
if (initFunctions.size() > 1) {
|
||||
for (int i = 1; i < initFunctions.size(); i++) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_DUPLICATE_FILE_INIT.name(),
|
||||
"A source file may declare at most one [Init] function",
|
||||
initFunctions.get(i).span());
|
||||
}
|
||||
}
|
||||
|
||||
final var hostPolicy = hostPolicy(ast, supplementalTopDecls);
|
||||
for (final var initFunction : initFunctions) {
|
||||
validateInitBody(initFunction.body(), hostPolicy, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLifecycleSignature(
|
||||
final PbsAst.FunctionDecl functionDecl,
|
||||
final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics) {
|
||||
final var valid = functionDecl.parameters().isEmpty()
|
||||
&& functionDecl.returnKind() == PbsAst.ReturnKind.EXPLICIT_UNIT
|
||||
&& functionDecl.returnType() != null
|
||||
&& functionDecl.returnType().kind() == PbsAst.TypeRefKind.UNIT
|
||||
&& functionDecl.resultErrorType() == null;
|
||||
if (valid) {
|
||||
return;
|
||||
}
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_INVALID_LIFECYCLE_SIGNATURE.name(),
|
||||
"Lifecycle functions marked with [%s] must have signature 'fn name() -> void'".formatted(
|
||||
functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.INIT ? "Init" : "Frame"),
|
||||
functionDecl.span());
|
||||
}
|
||||
|
||||
private HostPolicy hostPolicy(
|
||||
final PbsAst.File ast,
|
||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls) {
|
||||
final var methodsByOwner = new HashMap<String, Set<String>>();
|
||||
final var initAllowedByOwner = new HashMap<String, Set<String>>();
|
||||
collectHostPolicy(ast.topDecls(), methodsByOwner, initAllowedByOwner);
|
||||
collectHostPolicy(supplementalTopDecls, methodsByOwner, initAllowedByOwner);
|
||||
return new HostPolicy(methodsByOwner, initAllowedByOwner);
|
||||
}
|
||||
|
||||
private void collectHostPolicy(
|
||||
final Iterable<PbsAst.TopDecl> topDecls,
|
||||
final Map<String, Set<String>> methodsByOwner,
|
||||
final Map<String, Set<String>> initAllowedByOwner) {
|
||||
for (final var topDecl : topDecls) {
|
||||
if (!(topDecl instanceof PbsAst.HostDecl hostDecl)) {
|
||||
continue;
|
||||
}
|
||||
for (final var signature : hostDecl.signatures()) {
|
||||
methodsByOwner.computeIfAbsent(hostDecl.name(), ignored -> new HashSet<>()).add(signature.name());
|
||||
if (hasInitAllowed(signature.attributes())) {
|
||||
initAllowedByOwner.computeIfAbsent(hostDecl.name(), ignored -> new HashSet<>()).add(signature.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasInitAllowed(final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||
for (final var attribute : attributes) {
|
||||
if (ATTR_INIT_ALLOWED.equals(attribute.name())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void validateInitBody(
|
||||
final PbsAst.Block block,
|
||||
final HostPolicy hostPolicy,
|
||||
final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics) {
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
for (final var statement : block.statements()) {
|
||||
validateStatement(statement, hostPolicy, diagnostics);
|
||||
}
|
||||
validateExpression(block.tailExpression(), hostPolicy, diagnostics);
|
||||
}
|
||||
|
||||
private void validateStatement(
|
||||
final PbsAst.Statement statement,
|
||||
final HostPolicy hostPolicy,
|
||||
final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics) {
|
||||
if (statement instanceof PbsAst.LetStatement letStatement) {
|
||||
validateExpression(letStatement.initializer(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (statement instanceof PbsAst.AssignStatement assignStatement) {
|
||||
validateExpression(assignStatement.value(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (statement instanceof PbsAst.ReturnStatement returnStatement) {
|
||||
validateExpression(returnStatement.value(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (statement instanceof PbsAst.IfStatement ifStatement) {
|
||||
validateExpression(ifStatement.condition(), hostPolicy, diagnostics);
|
||||
validateInitBody(ifStatement.thenBlock(), hostPolicy, diagnostics);
|
||||
validateStatement(ifStatement.elseIf(), hostPolicy, diagnostics);
|
||||
validateInitBody(ifStatement.elseBlock(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (statement instanceof PbsAst.ForStatement forStatement) {
|
||||
validateExpression(forStatement.fromExpression(), hostPolicy, diagnostics);
|
||||
validateExpression(forStatement.untilExpression(), hostPolicy, diagnostics);
|
||||
validateExpression(forStatement.stepExpression(), hostPolicy, diagnostics);
|
||||
validateInitBody(forStatement.body(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (statement instanceof PbsAst.WhileStatement whileStatement) {
|
||||
validateExpression(whileStatement.condition(), hostPolicy, diagnostics);
|
||||
validateInitBody(whileStatement.body(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (statement instanceof PbsAst.ExpressionStatement expressionStatement) {
|
||||
validateExpression(expressionStatement.expression(), hostPolicy, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateExpression(
|
||||
final PbsAst.Expression expression,
|
||||
final HostPolicy hostPolicy,
|
||||
final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics) {
|
||||
if (expression == null) {
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.CallExpr callExpr) {
|
||||
validateHostCall(callExpr, hostPolicy, diagnostics);
|
||||
validateExpression(callExpr.callee(), hostPolicy, diagnostics);
|
||||
for (final var argument : callExpr.arguments()) {
|
||||
validateExpression(argument, hostPolicy, diagnostics);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.ApplyExpr applyExpr) {
|
||||
validateExpression(applyExpr.callee(), hostPolicy, diagnostics);
|
||||
validateExpression(applyExpr.argument(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.MemberExpr memberExpr) {
|
||||
validateExpression(memberExpr.receiver(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.UnaryExpr unaryExpr) {
|
||||
validateExpression(unaryExpr.expression(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.BinaryExpr binaryExpr) {
|
||||
validateExpression(binaryExpr.left(), hostPolicy, diagnostics);
|
||||
validateExpression(binaryExpr.right(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.GroupExpr groupExpr) {
|
||||
validateExpression(groupExpr.expression(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.NewExpr newExpr) {
|
||||
for (final var argument : newExpr.arguments()) {
|
||||
validateExpression(argument, hostPolicy, diagnostics);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.IfExpr ifExpr) {
|
||||
validateExpression(ifExpr.condition(), hostPolicy, diagnostics);
|
||||
validateInitBody(ifExpr.thenBlock(), hostPolicy, diagnostics);
|
||||
validateExpression(ifExpr.elseExpression(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.SwitchExpr switchExpr) {
|
||||
validateExpression(switchExpr.selector(), hostPolicy, diagnostics);
|
||||
for (final var arm : switchExpr.arms()) {
|
||||
validateInitBody(arm.block(), hostPolicy, diagnostics);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.HandleExpr handleExpr) {
|
||||
validateExpression(handleExpr.value(), hostPolicy, diagnostics);
|
||||
for (final var arm : handleExpr.arms()) {
|
||||
validateInitBody(arm.block(), hostPolicy, diagnostics);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.ElseExpr elseExpr) {
|
||||
validateExpression(elseExpr.optionalExpression(), hostPolicy, diagnostics);
|
||||
validateExpression(elseExpr.fallbackExpression(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.PropagateExpr propagateExpr) {
|
||||
validateExpression(propagateExpr.expression(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.BindExpr bindExpr) {
|
||||
validateExpression(bindExpr.contextExpression(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.AsExpr asExpr) {
|
||||
validateExpression(asExpr.expression(), hostPolicy, diagnostics);
|
||||
return;
|
||||
}
|
||||
if (expression instanceof PbsAst.BlockExpr blockExpr) {
|
||||
validateInitBody(blockExpr.block(), hostPolicy, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateHostCall(
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final HostPolicy hostPolicy,
|
||||
final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics) {
|
||||
if (!(callExpr.callee() instanceof PbsAst.MemberExpr memberExpr)) {
|
||||
return;
|
||||
}
|
||||
if (!(memberExpr.receiver() instanceof PbsAst.IdentifierExpr receiverIdentifier)) {
|
||||
return;
|
||||
}
|
||||
if (!hostPolicy.isHostMethod(receiverIdentifier.name(), memberExpr.memberName())) {
|
||||
return;
|
||||
}
|
||||
if (hostPolicy.isInitAllowed(receiverIdentifier.name(), memberExpr.memberName())) {
|
||||
return;
|
||||
}
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_INIT_HOST_CALL_NOT_ALLOWED.name(),
|
||||
"Host call '%s.%s' is not allowed during [Init] without InitAllowed".formatted(
|
||||
receiverIdentifier.name(),
|
||||
memberExpr.memberName()),
|
||||
callExpr.span());
|
||||
}
|
||||
|
||||
private record HostPolicy(
|
||||
Map<String, Set<String>> methodsByOwner,
|
||||
Map<String, Set<String>> initAllowedByOwner) {
|
||||
boolean isHostMethod(final String owner, final String method) {
|
||||
return methodsByOwner.getOrDefault(owner, Set.of()).contains(method);
|
||||
}
|
||||
|
||||
boolean isInitAllowed(final String owner, final String method) {
|
||||
return initAllowedByOwner.getOrDefault(owner, Set.of()).contains(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,11 @@ public enum PbsSemanticsErrors {
|
||||
E_SEM_MISSING_REQUIRED_RESERVED_ATTRIBUTE,
|
||||
E_SEM_DUPLICATE_RESERVED_ATTRIBUTE,
|
||||
E_SEM_MALFORMED_RESERVED_ATTRIBUTE,
|
||||
E_SEM_INVALID_LIFECYCLE_SIGNATURE,
|
||||
E_SEM_DUPLICATE_FILE_INIT,
|
||||
E_SEM_DUPLICATE_PROJECT_FRAME,
|
||||
E_SEM_MISSING_PROJECT_FRAME,
|
||||
E_SEM_INIT_HOST_CALL_NOT_ALLOWED,
|
||||
E_SEM_MISSING_CONST_TYPE_ANNOTATION,
|
||||
E_SEM_MISSING_CONST_INITIALIZER,
|
||||
E_SEM_MISSING_GLOBAL_TYPE_ANNOTATION,
|
||||
|
||||
@ -21,6 +21,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
private final PbsFrontendCompiler frontendCompiler;
|
||||
private final PbsModuleAssemblyService moduleAssemblyService;
|
||||
private final PbsImportedSemanticContextService importedSemanticContextService;
|
||||
private final PbsProjectLifecycleSemanticsValidator projectLifecycleSemanticsValidator = new PbsProjectLifecycleSemanticsValidator();
|
||||
|
||||
public PBSFrontendPhaseService() {
|
||||
this(new ResourceStdlibEnvironmentResolver(), new InterfaceModuleLoader());
|
||||
@ -51,6 +52,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
final var assembly = moduleAssemblyService.assemble(ctx, nameTable, diagnostics, issues);
|
||||
final var parsedSourceFiles = assembly.parsedSourceFiles();
|
||||
final var importedSemanticContexts = importedSemanticContextService.build(parsedSourceFiles, assembly.moduleTable());
|
||||
projectLifecycleSemanticsValidator.validate(parsedSourceFiles.asList(), diagnostics);
|
||||
final var failedModuleIds = assembly.mutableFailedModuleIds();
|
||||
final var moduleDependencyGraph = assembly.moduleDependencyGraph();
|
||||
final var canonicalModulePool = assembly.canonicalModulePool();
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
package p.studio.compiler.services;
|
||||
|
||||
import p.studio.compiler.models.SourceKind;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
final class PbsProjectLifecycleSemanticsValidator {
|
||||
|
||||
void validate(
|
||||
final java.util.List<PbsParsedSourceFile> parsedSourceFiles,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var frames = new ArrayList<PbsAst.FunctionDecl>();
|
||||
var sawLifecycleMarker = false;
|
||||
|
||||
for (final var parsedSource : parsedSourceFiles) {
|
||||
if (parsedSource.sourceKind() == SourceKind.SDK_INTERFACE) {
|
||||
continue;
|
||||
}
|
||||
for (final var topDecl : parsedSource.ast().topDecls()) {
|
||||
if (!(topDecl instanceof PbsAst.FunctionDecl functionDecl)) {
|
||||
continue;
|
||||
}
|
||||
if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.NONE) {
|
||||
continue;
|
||||
}
|
||||
sawLifecycleMarker = true;
|
||||
if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.FRAME) {
|
||||
frames.add(functionDecl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sawLifecycleMarker) {
|
||||
return;
|
||||
}
|
||||
if (frames.isEmpty()) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MISSING_PROJECT_FRAME.name(),
|
||||
"Lifecycle-marked executable sources must declare exactly one [Frame] function",
|
||||
parsedSourceFiles.getFirst().ast().span());
|
||||
return;
|
||||
}
|
||||
if (frames.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
for (int i = 1; i < frames.size(); i++) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_DUPLICATE_PROJECT_FRAME.name(),
|
||||
"Executable projects may declare only one [Frame] function",
|
||||
frames.get(i).span());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -203,6 +203,47 @@ class PbsSemanticsDeclarationsTest {
|
||||
assertEquals(4, invalidCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectInvalidLifecycleSignaturesAndMultipleFileInitMarkers() {
|
||||
final var source = """
|
||||
[Init]
|
||||
fn boot(v: int) -> void { return; }
|
||||
|
||||
[Init]
|
||||
fn warmup() -> void { return; }
|
||||
|
||||
[Frame]
|
||||
fn frame() { return; }
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||
|
||||
final var lifecycleSignatureCount = diagnostics.stream()
|
||||
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_LIFECYCLE_SIGNATURE.name()))
|
||||
.count();
|
||||
final var duplicateInitCount = diagnostics.stream()
|
||||
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_FILE_INIT.name()))
|
||||
.count();
|
||||
|
||||
assertEquals(2, lifecycleSignatureCount);
|
||||
assertEquals(1, duplicateInitCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectInitAllowedOutsideHostSignatures() {
|
||||
final var source = """
|
||||
[InitAllowed]
|
||||
declare const LIMIT: int = 1;
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowSelfInStructServiceMethodsAndCtors() {
|
||||
final var source = """
|
||||
|
||||
@ -1227,6 +1227,199 @@ class PBSFrontendPhaseServiceTest {
|
||||
assertEquals(2, cycleCount, diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectDuplicateFrameMarkersAcrossExecutableProject() throws IOException {
|
||||
final var projectRoot = tempDir.resolve("project-duplicate-frame-markers");
|
||||
final var sourceRoot = projectRoot.resolve("src");
|
||||
final var moduleAPath = sourceRoot.resolve("a");
|
||||
final var moduleBPath = sourceRoot.resolve("b");
|
||||
Files.createDirectories(moduleAPath);
|
||||
Files.createDirectories(moduleBPath);
|
||||
|
||||
final var sourceA = moduleAPath.resolve("source.pbs");
|
||||
final var barrelA = moduleAPath.resolve("mod.barrel");
|
||||
Files.writeString(sourceA, """
|
||||
[Frame]
|
||||
fn frame_a() -> void { return; }
|
||||
""");
|
||||
Files.writeString(barrelA, "pub fn frame_a() -> void;");
|
||||
|
||||
final var sourceB = moduleBPath.resolve("source.pbs");
|
||||
final var barrelB = moduleBPath.resolve("mod.barrel");
|
||||
Files.writeString(sourceB, """
|
||||
[Frame]
|
||||
fn frame_b() -> void { return; }
|
||||
""");
|
||||
Files.writeString(barrelB, "pub fn frame_b() -> void;");
|
||||
|
||||
final var projectTable = new ProjectTable();
|
||||
final var fileTable = new FileTable(1);
|
||||
final var projectId = projectTable.register(ProjectDescriptor.builder()
|
||||
.rootPath(projectRoot)
|
||||
.name("app")
|
||||
.version("1.0.0")
|
||||
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
|
||||
.build());
|
||||
|
||||
registerFile(projectId, projectRoot, sourceA, fileTable);
|
||||
registerFile(projectId, projectRoot, barrelA, fileTable);
|
||||
registerFile(projectId, projectRoot, sourceB, fileTable);
|
||||
registerFile(projectId, projectRoot, barrelB, fileTable);
|
||||
|
||||
final var ctx = new FrontendPhaseContext(
|
||||
projectTable,
|
||||
fileTable,
|
||||
new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
new PBSFrontendPhaseService().compile(
|
||||
ctx,
|
||||
diagnostics,
|
||||
LogAggregator.empty(),
|
||||
BuildingIssueSink.empty());
|
||||
|
||||
final var duplicateFrameCount = diagnostics.stream()
|
||||
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_PROJECT_FRAME.name()))
|
||||
.count();
|
||||
assertEquals(1, duplicateFrameCount, diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectHostCallsInInitWithoutInitAllowedAndAcceptWhenPresent() throws IOException {
|
||||
final var projectRoot = tempDir.resolve("project-init-host-calls");
|
||||
final var sourceRoot = projectRoot.resolve("src");
|
||||
Files.createDirectories(sourceRoot);
|
||||
|
||||
final var deniedSource = sourceRoot.resolve("denied.pbs");
|
||||
final var deniedBarrel = sourceRoot.resolve("mod.barrel");
|
||||
Files.writeString(deniedSource, """
|
||||
import { Gfx } from @sdk:gfx;
|
||||
|
||||
[Init]
|
||||
fn boot() -> void {
|
||||
Gfx.clear();
|
||||
}
|
||||
|
||||
[Frame]
|
||||
fn frame() -> void { return; }
|
||||
""");
|
||||
Files.writeString(deniedBarrel, """
|
||||
pub fn boot() -> void;
|
||||
pub fn frame() -> void;
|
||||
""");
|
||||
|
||||
final var projectTable = new ProjectTable();
|
||||
final var fileTable = new FileTable(1);
|
||||
final var projectId = projectTable.register(ProjectDescriptor.builder()
|
||||
.rootPath(projectRoot)
|
||||
.name("app")
|
||||
.version("1.0.0")
|
||||
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
|
||||
.build());
|
||||
|
||||
registerFile(projectId, projectRoot, deniedSource, fileTable);
|
||||
registerFile(projectId, projectRoot, deniedBarrel, fileTable);
|
||||
|
||||
final var sdkModuleDenied = new StdlibModuleSource(
|
||||
"sdk",
|
||||
ReadOnlyList.wrap(List.of("gfx")),
|
||||
ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
|
||||
"gfx.pbs",
|
||||
"""
|
||||
declare host Gfx {
|
||||
[Host(module = "gfx", name = "clear", version = 1)]
|
||||
fn clear() -> void;
|
||||
}
|
||||
"""))),
|
||||
"pub host Gfx;");
|
||||
final var deniedFrontendService = new PBSFrontendPhaseService(
|
||||
resolverForMajor(11, sdkModuleDenied),
|
||||
new InterfaceModuleLoader(3_200_000));
|
||||
|
||||
final var deniedCtx = new FrontendPhaseContext(
|
||||
projectTable,
|
||||
fileTable,
|
||||
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
|
||||
11);
|
||||
final var deniedDiagnostics = DiagnosticSink.empty();
|
||||
|
||||
deniedFrontendService.compile(
|
||||
deniedCtx,
|
||||
deniedDiagnostics,
|
||||
LogAggregator.empty(),
|
||||
BuildingIssueSink.empty());
|
||||
|
||||
assertTrue(deniedDiagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_INIT_HOST_CALL_NOT_ALLOWED.name())),
|
||||
deniedDiagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
||||
|
||||
final var allowedProjectRoot = tempDir.resolve("project-init-host-calls-allowed");
|
||||
final var allowedSourceRoot = allowedProjectRoot.resolve("src");
|
||||
Files.createDirectories(allowedSourceRoot);
|
||||
final var allowedSource = allowedSourceRoot.resolve("main.pbs");
|
||||
final var allowedBarrel = allowedSourceRoot.resolve("mod.barrel");
|
||||
Files.writeString(allowedSource, """
|
||||
import { Gfx } from @sdk:gfx;
|
||||
|
||||
[Init]
|
||||
fn boot() -> void {
|
||||
Gfx.clear();
|
||||
}
|
||||
|
||||
[Frame]
|
||||
fn frame() -> void { return; }
|
||||
""");
|
||||
Files.writeString(allowedBarrel, """
|
||||
pub fn boot() -> void;
|
||||
pub fn frame() -> void;
|
||||
""");
|
||||
|
||||
final var allowedProjectTable = new ProjectTable();
|
||||
final var allowedFileTable = new FileTable(1);
|
||||
final var allowedProjectId = allowedProjectTable.register(ProjectDescriptor.builder()
|
||||
.rootPath(allowedProjectRoot)
|
||||
.name("app")
|
||||
.version("1.0.0")
|
||||
.sourceRoots(ReadOnlyList.wrap(List.of(allowedSourceRoot)))
|
||||
.build());
|
||||
registerFile(allowedProjectId, allowedProjectRoot, allowedSource, allowedFileTable);
|
||||
registerFile(allowedProjectId, allowedProjectRoot, allowedBarrel, allowedFileTable);
|
||||
|
||||
final var sdkModuleAllowed = new StdlibModuleSource(
|
||||
"sdk",
|
||||
ReadOnlyList.wrap(List.of("gfx")),
|
||||
ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
|
||||
"gfx.pbs",
|
||||
"""
|
||||
declare host Gfx {
|
||||
[Host(module = "gfx", name = "clear", version = 1)]
|
||||
[InitAllowed]
|
||||
fn clear() -> void;
|
||||
}
|
||||
"""))),
|
||||
"pub host Gfx;");
|
||||
final var allowedFrontendService = new PBSFrontendPhaseService(
|
||||
resolverForMajor(12, sdkModuleAllowed),
|
||||
new InterfaceModuleLoader(3_300_000));
|
||||
|
||||
final var allowedCtx = new FrontendPhaseContext(
|
||||
allowedProjectTable,
|
||||
allowedFileTable,
|
||||
new BuildStack(ReadOnlyList.wrap(List.of(allowedProjectId))),
|
||||
12);
|
||||
final var allowedDiagnostics = DiagnosticSink.empty();
|
||||
|
||||
allowedFrontendService.compile(
|
||||
allowedCtx,
|
||||
allowedDiagnostics,
|
||||
LogAggregator.empty(),
|
||||
BuildingIssueSink.empty());
|
||||
|
||||
assertTrue(allowedDiagnostics.stream().noneMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_INIT_HOST_CALL_NOT_ALLOWED.name())),
|
||||
allowedDiagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
||||
}
|
||||
|
||||
private void registerFile(
|
||||
final ProjectId projectId,
|
||||
final Path projectRoot,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user