implements PR017
This commit is contained in:
parent
e4be2a033f
commit
2041e34496
@ -1,45 +0,0 @@
|
|||||||
# PR-017 - PBS Const Evaluation and Dependency Rules
|
|
||||||
|
|
||||||
## Briefing
|
|
||||||
|
|
||||||
`declare const` validation only checks explicit type and initializer presence. This PR implements constant-expression legality, dependency ordering, cycle detection, and initializer type compatibility.
|
|
||||||
|
|
||||||
## Motivation
|
|
||||||
|
|
||||||
Const declarations are part of static semantics and must be deterministic across files.
|
|
||||||
|
|
||||||
## Target
|
|
||||||
|
|
||||||
- Const semantic validation layer.
|
|
||||||
- Diagnostic coverage for constant expression failures.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Enforce allowed constant-expression subset.
|
|
||||||
- Resolve const dependencies module-wide, independent of source-file order.
|
|
||||||
- Reject cycles and unresolved const references.
|
|
||||||
- Validate initializer type compatibility with declared const type.
|
|
||||||
|
|
||||||
## Method
|
|
||||||
|
|
||||||
- Build const dependency graph from top-level declarations.
|
|
||||||
- Evaluate in topological order.
|
|
||||||
- Emit stable diagnostics for disallowed forms and graph failures.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Non-constant initializer forms are rejected deterministically.
|
|
||||||
- Cross-file const references resolve independent of source order.
|
|
||||||
- Cycles are rejected deterministically.
|
|
||||||
- Incompatible const initializer types are rejected.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
- Add const-expression positive/negative fixtures.
|
|
||||||
- Add cross-file dependency and cycle tests.
|
|
||||||
- Add type-compatibility tests.
|
|
||||||
|
|
||||||
## Non-Goals
|
|
||||||
|
|
||||||
- General compile-time function execution.
|
|
||||||
- Runtime const materialization strategy changes.
|
|
||||||
@ -0,0 +1,265 @@
|
|||||||
|
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.pbs.semantics.PbsFlowSemanticSupport.Model;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.TypeView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
final class PbsConstSemanticsValidator {
|
||||||
|
private final PbsFlowTypeOps typeOps = new PbsFlowTypeOps();
|
||||||
|
|
||||||
|
private record ConstAnalysis(
|
||||||
|
TypeView type,
|
||||||
|
boolean constant,
|
||||||
|
Span invalidSpan,
|
||||||
|
Set<String> dependencies) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
|
||||||
|
final var model = Model.from(ast);
|
||||||
|
final var constByName = collectConstDeclarations(ast);
|
||||||
|
final var dependencyGraph = new LinkedHashMap<String, Set<String>>();
|
||||||
|
|
||||||
|
for (final var entry : constByName.entrySet()) {
|
||||||
|
final var constDecl = entry.getValue();
|
||||||
|
if (constDecl.explicitType() == null || constDecl.initializer() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var declaredType = typeOps.typeFromTypeRef(constDecl.explicitType(), model, null);
|
||||||
|
final var analysis = analyzeConstExpression(constDecl.initializer(), constByName, model, diagnostics);
|
||||||
|
dependencyGraph.put(entry.getKey(), analysis.dependencies());
|
||||||
|
|
||||||
|
if (!analysis.constant()) {
|
||||||
|
final var invalidSpan = analysis.invalidSpan() == null ? constDecl.initializer().span() : analysis.invalidSpan();
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_CONST_NON_CONSTANT_INITIALIZER.name(),
|
||||||
|
"Const initializer for '%s' is not a constant expression".formatted(constDecl.name()),
|
||||||
|
invalidSpan);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!typeOps.compatible(analysis.type(), declaredType)) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_CONST_INITIALIZER_TYPE_MISMATCH.name(),
|
||||||
|
"Const initializer type is incompatible with declared type for '%s'".formatted(constDecl.name()),
|
||||||
|
constDecl.initializer().span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateConstCycles(constByName, dependencyGraph, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedHashMap<String, PbsAst.ConstDecl> collectConstDeclarations(final PbsAst.File ast) {
|
||||||
|
final var constByName = new LinkedHashMap<String, PbsAst.ConstDecl>();
|
||||||
|
for (final var topDecl : ast.topDecls()) {
|
||||||
|
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||||
|
constByName.putIfAbsent(constDecl.name(), constDecl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return constByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConstAnalysis analyzeConstExpression(
|
||||||
|
final PbsAst.Expression expression,
|
||||||
|
final Map<String, PbsAst.ConstDecl> constByName,
|
||||||
|
final Model model,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
if (expression instanceof PbsAst.IntLiteralExpr || expression instanceof PbsAst.BoundedLiteralExpr) {
|
||||||
|
return new ConstAnalysis(TypeView.intType(), true, null, Set.of());
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.FloatLiteralExpr) {
|
||||||
|
return new ConstAnalysis(TypeView.floatType(), true, null, Set.of());
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.StringLiteralExpr) {
|
||||||
|
return new ConstAnalysis(TypeView.str(), true, null, Set.of());
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.BoolLiteralExpr) {
|
||||||
|
return new ConstAnalysis(TypeView.bool(), true, null, Set.of());
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.GroupExpr groupExpr) {
|
||||||
|
return analyzeConstExpression(groupExpr.expression(), constByName, model, diagnostics);
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.IdentifierExpr identifierExpr) {
|
||||||
|
final var depName = identifierExpr.name();
|
||||||
|
final var depDecl = constByName.get(depName);
|
||||||
|
if (depDecl == null || depDecl.explicitType() == null || depDecl.initializer() == null) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_CONST_UNRESOLVED_REFERENCE.name(),
|
||||||
|
"Const initializer references unresolved const '%s'".formatted(depName),
|
||||||
|
identifierExpr.span());
|
||||||
|
return new ConstAnalysis(TypeView.unknown(), false, identifierExpr.span(), Set.of());
|
||||||
|
}
|
||||||
|
return new ConstAnalysis(
|
||||||
|
typeOps.typeFromTypeRef(depDecl.explicitType(), model, null),
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
Set.of(depName));
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.MemberExpr memberExpr) {
|
||||||
|
if (memberExpr.receiver() instanceof PbsAst.IdentifierExpr enumIdentifier) {
|
||||||
|
final var cases = model.enums.get(enumIdentifier.name());
|
||||||
|
if (cases != null && cases.contains(memberExpr.memberName())) {
|
||||||
|
return new ConstAnalysis(TypeView.enumType(enumIdentifier.name()), true, null, Set.of());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ConstAnalysis(TypeView.unknown(), false, memberExpr.span(), Set.of());
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.UnaryExpr unaryExpr) {
|
||||||
|
final var operand = analyzeConstExpression(unaryExpr.expression(), constByName, model, diagnostics);
|
||||||
|
if (!operand.constant()) {
|
||||||
|
return operand;
|
||||||
|
}
|
||||||
|
if ("-".equals(unaryExpr.operator()) && isNumeric(operand.type())) {
|
||||||
|
return operand;
|
||||||
|
}
|
||||||
|
if (("!".equals(unaryExpr.operator()) || "not".equals(unaryExpr.operator())) && typeOps.isBool(operand.type())) {
|
||||||
|
return new ConstAnalysis(TypeView.bool(), true, null, operand.dependencies());
|
||||||
|
}
|
||||||
|
return new ConstAnalysis(TypeView.unknown(), false, unaryExpr.span(), operand.dependencies());
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.BinaryExpr binaryExpr) {
|
||||||
|
final var left = analyzeConstExpression(binaryExpr.left(), constByName, model, diagnostics);
|
||||||
|
final var right = analyzeConstExpression(binaryExpr.right(), constByName, model, diagnostics);
|
||||||
|
final var deps = mergeDependencies(left.dependencies(), right.dependencies());
|
||||||
|
if (!left.constant()) {
|
||||||
|
return new ConstAnalysis(TypeView.unknown(), false, left.invalidSpan(), deps);
|
||||||
|
}
|
||||||
|
if (!right.constant()) {
|
||||||
|
return new ConstAnalysis(TypeView.unknown(), false, right.invalidSpan(), deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
final var operator = binaryExpr.operator();
|
||||||
|
if (isArithmeticOperator(operator)) {
|
||||||
|
if (!isNumeric(left.type()) || !isNumeric(right.type())) {
|
||||||
|
return new ConstAnalysis(TypeView.unknown(), false, binaryExpr.span(), deps);
|
||||||
|
}
|
||||||
|
return new ConstAnalysis(typeOps.inferBinaryResult(operator, left.type(), right.type()), true, null, deps);
|
||||||
|
}
|
||||||
|
if (isComparisonOperator(operator)) {
|
||||||
|
if (!typeOps.compatible(left.type(), right.type())) {
|
||||||
|
return new ConstAnalysis(TypeView.unknown(), false, binaryExpr.span(), deps);
|
||||||
|
}
|
||||||
|
return new ConstAnalysis(TypeView.bool(), true, null, deps);
|
||||||
|
}
|
||||||
|
if (isBooleanOperator(operator)) {
|
||||||
|
if (!typeOps.isBool(left.type()) || !typeOps.isBool(right.type())) {
|
||||||
|
return new ConstAnalysis(TypeView.unknown(), false, binaryExpr.span(), deps);
|
||||||
|
}
|
||||||
|
return new ConstAnalysis(TypeView.bool(), true, null, deps);
|
||||||
|
}
|
||||||
|
return new ConstAnalysis(TypeView.unknown(), false, binaryExpr.span(), deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConstAnalysis(TypeView.unknown(), false, expression.span(), Set.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateConstCycles(
|
||||||
|
final Map<String, PbsAst.ConstDecl> constByName,
|
||||||
|
final Map<String, Set<String>> dependencyGraph,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var indegree = new HashMap<String, Integer>();
|
||||||
|
final var reverse = new HashMap<String, Set<String>>();
|
||||||
|
for (final var node : dependencyGraph.keySet()) {
|
||||||
|
indegree.put(node, 0);
|
||||||
|
reverse.put(node, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final var entry : dependencyGraph.entrySet()) {
|
||||||
|
for (final var dep : entry.getValue()) {
|
||||||
|
if (!dependencyGraph.containsKey(dep)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
indegree.put(entry.getKey(), indegree.get(entry.getKey()) + 1);
|
||||||
|
reverse.get(dep).add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final var ready = new PriorityQueue<String>();
|
||||||
|
for (final var entry : indegree.entrySet()) {
|
||||||
|
if (entry.getValue() == 0) {
|
||||||
|
ready.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var visited = 0;
|
||||||
|
while (!ready.isEmpty()) {
|
||||||
|
final var current = ready.remove();
|
||||||
|
visited++;
|
||||||
|
for (final var dependent : reverse.getOrDefault(current, Set.of())) {
|
||||||
|
final var next = indegree.get(dependent) - 1;
|
||||||
|
indegree.put(dependent, next);
|
||||||
|
if (next == 0) {
|
||||||
|
ready.add(dependent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visited == dependencyGraph.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var cyclic = new ArrayList<String>();
|
||||||
|
for (final var entry : indegree.entrySet()) {
|
||||||
|
if (entry.getValue() > 0) {
|
||||||
|
cyclic.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cyclic.sort(String::compareTo);
|
||||||
|
|
||||||
|
for (final var constName : cyclic) {
|
||||||
|
final var constDecl = constByName.get(constName);
|
||||||
|
if (constDecl == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_CONST_CYCLIC_DEPENDENCY.name(),
|
||||||
|
"Cyclic const dependency detected involving '%s'".formatted(constName),
|
||||||
|
constDecl.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> mergeDependencies(final Set<String> left, final Set<String> right) {
|
||||||
|
final var merged = new HashSet<String>();
|
||||||
|
merged.addAll(left);
|
||||||
|
merged.addAll(right);
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNumeric(final TypeView type) {
|
||||||
|
return typeOps.isInt(type) || typeOps.isFloat(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isArithmeticOperator(final String operator) {
|
||||||
|
return "+".equals(operator)
|
||||||
|
|| "-".equals(operator)
|
||||||
|
|| "*".equals(operator)
|
||||||
|
|| "/".equals(operator)
|
||||||
|
|| "%".equals(operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isComparisonOperator(final String operator) {
|
||||||
|
return "==".equals(operator)
|
||||||
|
|| "!=".equals(operator)
|
||||||
|
|| "<".equals(operator)
|
||||||
|
|| "<=".equals(operator)
|
||||||
|
|| ">".equals(operator)
|
||||||
|
|| ">=".equals(operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBooleanOperator(final String operator) {
|
||||||
|
return "and".equals(operator)
|
||||||
|
|| "or".equals(operator)
|
||||||
|
|| "&&".equals(operator)
|
||||||
|
|| "||".equals(operator);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import p.studio.utilities.structures.ReadOnlyList;
|
|||||||
|
|
||||||
public final class PbsDeclarationSemanticsValidator {
|
public final class PbsDeclarationSemanticsValidator {
|
||||||
private final NameTable nameTable = new NameTable();
|
private final NameTable nameTable = new NameTable();
|
||||||
|
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
||||||
|
|
||||||
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
|
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
|
||||||
final var binder = new PbsNamespaceBinder(nameTable, diagnostics);
|
final var binder = new PbsNamespaceBinder(nameTable, diagnostics);
|
||||||
@ -76,6 +77,8 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
validateImplementsDeclaration(implementsDecl, binder, rules);
|
validateImplementsDeclaration(implementsDecl, binder, rules);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constSemanticsValidator.validate(ast, diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateStructDeclaration(
|
private void validateStructDeclaration(
|
||||||
|
|||||||
@ -12,6 +12,10 @@ public enum PbsSemanticsErrors {
|
|||||||
E_SEM_INVALID_OPTIONAL_VOID_TYPE_SURFACE,
|
E_SEM_INVALID_OPTIONAL_VOID_TYPE_SURFACE,
|
||||||
E_SEM_MISSING_CONST_TYPE_ANNOTATION,
|
E_SEM_MISSING_CONST_TYPE_ANNOTATION,
|
||||||
E_SEM_MISSING_CONST_INITIALIZER,
|
E_SEM_MISSING_CONST_INITIALIZER,
|
||||||
|
E_SEM_CONST_NON_CONSTANT_INITIALIZER,
|
||||||
|
E_SEM_CONST_INITIALIZER_TYPE_MISMATCH,
|
||||||
|
E_SEM_CONST_CYCLIC_DEPENDENCY,
|
||||||
|
E_SEM_CONST_UNRESOLVED_REFERENCE,
|
||||||
E_SEM_INVALID_RETURN_INSIDE_CTOR,
|
E_SEM_INVALID_RETURN_INSIDE_CTOR,
|
||||||
E_SEM_APPLY_NON_CALLABLE_TARGET,
|
E_SEM_APPLY_NON_CALLABLE_TARGET,
|
||||||
E_SEM_APPLY_UNRESOLVED_OVERLOAD,
|
E_SEM_APPLY_UNRESOLVED_OVERLOAD,
|
||||||
|
|||||||
@ -0,0 +1,96 @@
|
|||||||
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||||
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
|
class PbsSemanticsConstTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstDependenciesIndependentOfSourceOrder() {
|
||||||
|
final var source = """
|
||||||
|
declare enum Mode(Idle, Run);
|
||||||
|
declare const C: int = B + 1;
|
||||||
|
declare const B: int = A + 1;
|
||||||
|
declare const A: int = 1;
|
||||||
|
declare const M: Mode = Mode.Run;
|
||||||
|
|
||||||
|
fn use() -> int {
|
||||||
|
return C;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||||
|
|
||||||
|
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_UNRESOLVED_REFERENCE.name())));
|
||||||
|
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_CYCLIC_DEPENDENCY.name())));
|
||||||
|
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_NON_CONSTANT_INITIALIZER.name())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectNonConstantConstInitializers() {
|
||||||
|
final var source = """
|
||||||
|
fn inc(v: int) -> int { return v + 1; }
|
||||||
|
declare struct Box(pub mut value: int);
|
||||||
|
|
||||||
|
declare const A: int = inc(1);
|
||||||
|
declare const B: int = new Box(1);
|
||||||
|
declare const C: int = if true { 1 } else { 2 };
|
||||||
|
declare const D: int = some(1);
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||||
|
|
||||||
|
final var nonConstCount = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_NON_CONSTANT_INITIALIZER.name()))
|
||||||
|
.count();
|
||||||
|
assertEquals(4, nonConstCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectConstInitializerTypeMismatchAndUnresolvedReference() {
|
||||||
|
final var source = """
|
||||||
|
declare const A: int = "wrong";
|
||||||
|
declare const B: int = Missing + 1;
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||||
|
|
||||||
|
final var mismatchCount = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_INITIALIZER_TYPE_MISMATCH.name()))
|
||||||
|
.count();
|
||||||
|
final var unresolvedCount = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_UNRESOLVED_REFERENCE.name()))
|
||||||
|
.count();
|
||||||
|
|
||||||
|
assertEquals(1, mismatchCount);
|
||||||
|
assertEquals(1, unresolvedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectCyclicConstDependencies() {
|
||||||
|
final var source = """
|
||||||
|
declare const A: int = B + 1;
|
||||||
|
declare const B: int = C + 1;
|
||||||
|
declare const C: int = A + 1;
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||||
|
|
||||||
|
final var cycleCount = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_CYCLIC_DEPENDENCY.name()))
|
||||||
|
.count();
|
||||||
|
assertEquals(3, cycleCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user