implements PR-19.5 global dependency graph and cycle validation
This commit is contained in:
parent
71a993ad4a
commit
2003fc749e
@ -90,6 +90,7 @@ public final class PbsFrontendCompiler {
|
|||||||
nameTable,
|
nameTable,
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.empty(),
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
IRReservedMetadata.empty());
|
IRReservedMetadata.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +105,7 @@ public final class PbsFrontendCompiler {
|
|||||||
final NameTable nameTable,
|
final NameTable nameTable,
|
||||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||||
final ReadOnlyList<ImportedCallableSurface> importedCallables,
|
final ReadOnlyList<ImportedCallableSurface> importedCallables,
|
||||||
|
final ReadOnlyList<ImportedGlobalSurface> importedGlobals,
|
||||||
final IRReservedMetadata importedReservedMetadata) {
|
final IRReservedMetadata importedReservedMetadata) {
|
||||||
final var effectiveModuleId = moduleId == null ? ModuleId.none() : moduleId;
|
final var effectiveModuleId = moduleId == null ? ModuleId.none() : moduleId;
|
||||||
final var effectiveModulePool = modulePool == null ? ReadOnlyList.<ModuleReference>empty() : modulePool;
|
final var effectiveModulePool = modulePool == null ? ReadOnlyList.<ModuleReference>empty() : modulePool;
|
||||||
@ -114,11 +116,19 @@ public final class PbsFrontendCompiler {
|
|||||||
final var effectiveImportedCallables = importedCallables == null
|
final var effectiveImportedCallables = importedCallables == null
|
||||||
? ReadOnlyList.<ImportedCallableSurface>empty()
|
? ReadOnlyList.<ImportedCallableSurface>empty()
|
||||||
: importedCallables;
|
: importedCallables;
|
||||||
|
final var effectiveImportedGlobals = importedGlobals == null
|
||||||
|
? ReadOnlyList.<ImportedGlobalSurface>empty()
|
||||||
|
: importedGlobals;
|
||||||
final var effectiveImportedReservedMetadata = importedReservedMetadata == null
|
final var effectiveImportedReservedMetadata = importedReservedMetadata == null
|
||||||
? IRReservedMetadata.empty()
|
? IRReservedMetadata.empty()
|
||||||
: importedReservedMetadata;
|
: importedReservedMetadata;
|
||||||
final var semanticsErrorBaseline = diagnostics.errorCount();
|
final var semanticsErrorBaseline = diagnostics.errorCount();
|
||||||
new PbsDeclarationSemanticsValidator(effectiveNameTable).validate(ast, sourceKind, diagnostics);
|
new PbsDeclarationSemanticsValidator(effectiveNameTable).validate(
|
||||||
|
ast,
|
||||||
|
sourceKind,
|
||||||
|
effectiveModuleId,
|
||||||
|
effectiveImportedGlobals,
|
||||||
|
diagnostics);
|
||||||
flowSemanticsValidator.validate(ast, effectiveSupplementalTopDecls, diagnostics);
|
flowSemanticsValidator.validate(ast, effectiveSupplementalTopDecls, diagnostics);
|
||||||
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
||||||
return IRBackendFile.empty(fileId);
|
return IRBackendFile.empty(fileId);
|
||||||
@ -233,4 +243,28 @@ public final class PbsFrontendCompiler {
|
|||||||
moduleId = moduleId == null ? ModuleId.none() : moduleId;
|
moduleId = moduleId == null ? ModuleId.none() : moduleId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record ImportedGlobalDependency(
|
||||||
|
String localName,
|
||||||
|
ModuleId ownerModuleId,
|
||||||
|
String globalName) {
|
||||||
|
public ImportedGlobalDependency {
|
||||||
|
ownerModuleId = ownerModuleId == null ? ModuleId.none() : ownerModuleId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ImportedGlobalSurface(
|
||||||
|
ModuleId ownerModuleId,
|
||||||
|
String globalName,
|
||||||
|
String localName,
|
||||||
|
PbsAst.TypeRef explicitType,
|
||||||
|
PbsAst.Expression initializer,
|
||||||
|
ReadOnlyList<ImportedGlobalDependency> importedDependencies,
|
||||||
|
p.studio.compiler.source.Span span,
|
||||||
|
boolean directlyVisible) {
|
||||||
|
public ImportedGlobalSurface {
|
||||||
|
ownerModuleId = ownerModuleId == null ? ModuleId.none() : ownerModuleId;
|
||||||
|
importedDependencies = importedDependencies == null ? ReadOnlyList.empty() : importedDependencies;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
package p.studio.compiler.pbs.semantics;
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
import p.studio.compiler.models.SourceKind;
|
import p.studio.compiler.models.SourceKind;
|
||||||
|
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||||
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.identifiers.ModuleId;
|
||||||
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;
|
||||||
|
|
||||||
@ -25,6 +27,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
|
|
||||||
private final NameTable nameTable;
|
private final NameTable nameTable;
|
||||||
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
||||||
|
private final PbsGlobalSemanticsValidator globalSemanticsValidator = new PbsGlobalSemanticsValidator();
|
||||||
|
|
||||||
public PbsDeclarationSemanticsValidator(final NameTable nameTable) {
|
public PbsDeclarationSemanticsValidator(final NameTable nameTable) {
|
||||||
this.nameTable = nameTable == null ? new NameTable() : nameTable;
|
this.nameTable = nameTable == null ? new NameTable() : nameTable;
|
||||||
@ -38,6 +41,15 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
final SourceKind sourceKind,
|
final SourceKind sourceKind,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
|
validate(ast, sourceKind, ModuleId.none(), ReadOnlyList.empty(), diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final SourceKind sourceKind,
|
||||||
|
final ModuleId currentModuleId,
|
||||||
|
final ReadOnlyList<PbsFrontendCompiler.ImportedGlobalSurface> importedGlobals,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
final var binder = new PbsNamespaceBinder(nameTable, diagnostics);
|
final var binder = new PbsNamespaceBinder(nameTable, diagnostics);
|
||||||
final var rules = new PbsDeclarationRuleValidator(nameTable, diagnostics);
|
final var rules = new PbsDeclarationRuleValidator(nameTable, diagnostics);
|
||||||
final var interfaceModule = sourceKind == SourceKind.SDK_INTERFACE;
|
final var interfaceModule = sourceKind == SourceKind.SDK_INTERFACE;
|
||||||
@ -153,6 +165,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constSemanticsValidator.validate(ast, diagnostics);
|
constSemanticsValidator.validate(ast, diagnostics);
|
||||||
|
globalSemanticsValidator.validate(ast, currentModuleId, importedGlobals, diagnostics);
|
||||||
if (interfaceModule) {
|
if (interfaceModule) {
|
||||||
PbsBuiltinLayoutResolver.resolve(ast, diagnostics);
|
PbsBuiltinLayoutResolver.resolve(ast, diagnostics);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,76 @@
|
|||||||
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class PbsGlobalDependencySupport {
|
||||||
|
private PbsGlobalDependencySupport() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record GlobalRef(
|
||||||
|
ModuleId moduleId,
|
||||||
|
String globalName) {
|
||||||
|
public GlobalRef {
|
||||||
|
moduleId = moduleId == null ? ModuleId.none() : moduleId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LinkedHashSet<GlobalRef> collectDependencies(
|
||||||
|
final PbsAst.Expression expression,
|
||||||
|
final ModuleId currentModuleId,
|
||||||
|
final Set<String> localGlobals,
|
||||||
|
final Map<String, GlobalRef> importedGlobalsByLocalName) {
|
||||||
|
final var dependencies = new LinkedHashSet<GlobalRef>();
|
||||||
|
collectRecursive(expression, currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void collectRecursive(
|
||||||
|
final PbsAst.Expression expression,
|
||||||
|
final ModuleId currentModuleId,
|
||||||
|
final Set<String> localGlobals,
|
||||||
|
final Map<String, GlobalRef> importedGlobalsByLocalName,
|
||||||
|
final LinkedHashSet<GlobalRef> dependencies) {
|
||||||
|
if (expression == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.IdentifierExpr identifierExpr) {
|
||||||
|
if (localGlobals.contains(identifierExpr.name())) {
|
||||||
|
dependencies.add(new GlobalRef(currentModuleId, identifierExpr.name()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final var importedGlobal = importedGlobalsByLocalName.get(identifierExpr.name());
|
||||||
|
if (importedGlobal != null) {
|
||||||
|
dependencies.add(importedGlobal);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.GroupExpr groupExpr) {
|
||||||
|
collectRecursive(groupExpr.expression(), currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.UnaryExpr unaryExpr) {
|
||||||
|
collectRecursive(unaryExpr.expression(), currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.BinaryExpr binaryExpr) {
|
||||||
|
collectRecursive(binaryExpr.left(), currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
|
||||||
|
collectRecursive(binaryExpr.right(), currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.MemberExpr memberExpr) {
|
||||||
|
collectRecursive(memberExpr.receiver(), currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.NewExpr newExpr) {
|
||||||
|
for (final var argument : newExpr.arguments()) {
|
||||||
|
collectRecursive(argument, currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
|
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||||
|
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.identifiers.ModuleId;
|
||||||
|
import p.studio.utilities.structures.DependencyGraphAnaliser;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
final class PbsGlobalSemanticsValidator {
|
||||||
|
|
||||||
|
Analysis analyse(
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final ModuleId currentModuleId,
|
||||||
|
final p.studio.utilities.structures.ReadOnlyList<PbsFrontendCompiler.ImportedGlobalSurface> importedGlobals) {
|
||||||
|
final var normalizedModuleId = currentModuleId == null ? ModuleId.none() : currentModuleId;
|
||||||
|
final var localDeclsByName = collectLocalGlobalDecls(ast);
|
||||||
|
final var localGlobalNames = new LinkedHashSet<>(localDeclsByName.keySet());
|
||||||
|
final var importedDefinitionsByRef = new LinkedHashMap<PbsGlobalDependencySupport.GlobalRef, PbsFrontendCompiler.ImportedGlobalSurface>();
|
||||||
|
final var directlyVisibleImports = new LinkedHashMap<String, PbsGlobalDependencySupport.GlobalRef>();
|
||||||
|
|
||||||
|
for (final var importedGlobal : importedGlobals) {
|
||||||
|
final var ref = new PbsGlobalDependencySupport.GlobalRef(importedGlobal.ownerModuleId(), importedGlobal.globalName());
|
||||||
|
importedDefinitionsByRef.putIfAbsent(ref, importedGlobal);
|
||||||
|
if (importedGlobal.directlyVisible()) {
|
||||||
|
directlyVisibleImports.putIfAbsent(importedGlobal.localName(), ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final var globalNamesByModule = new LinkedHashMap<ModuleId, LinkedHashSet<String>>();
|
||||||
|
globalNamesByModule.put(normalizedModuleId, localGlobalNames);
|
||||||
|
for (final var ref : importedDefinitionsByRef.keySet()) {
|
||||||
|
globalNamesByModule.computeIfAbsent(ref.moduleId(), ignored -> new LinkedHashSet<>()).add(ref.globalName());
|
||||||
|
}
|
||||||
|
|
||||||
|
final var graph = new LinkedHashMap<PbsGlobalDependencySupport.GlobalRef, Set<PbsGlobalDependencySupport.GlobalRef>>();
|
||||||
|
final var spansByRef = new LinkedHashMap<PbsGlobalDependencySupport.GlobalRef, Span>();
|
||||||
|
|
||||||
|
for (final var entry : localDeclsByName.entrySet()) {
|
||||||
|
final var ref = new PbsGlobalDependencySupport.GlobalRef(normalizedModuleId, entry.getKey());
|
||||||
|
spansByRef.put(ref, entry.getValue().span());
|
||||||
|
graph.put(ref, PbsGlobalDependencySupport.collectDependencies(
|
||||||
|
entry.getValue().initializer(),
|
||||||
|
normalizedModuleId,
|
||||||
|
localGlobalNames,
|
||||||
|
directlyVisibleImports));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final var entry : importedDefinitionsByRef.entrySet()) {
|
||||||
|
final var surface = entry.getValue();
|
||||||
|
spansByRef.putIfAbsent(entry.getKey(), surface.span());
|
||||||
|
final var importedByLocalName = new LinkedHashMap<String, PbsGlobalDependencySupport.GlobalRef>();
|
||||||
|
for (final var importedDependency : surface.importedDependencies()) {
|
||||||
|
importedByLocalName.putIfAbsent(
|
||||||
|
importedDependency.localName(),
|
||||||
|
new PbsGlobalDependencySupport.GlobalRef(
|
||||||
|
importedDependency.ownerModuleId(),
|
||||||
|
importedDependency.globalName()));
|
||||||
|
}
|
||||||
|
graph.putIfAbsent(entry.getKey(), PbsGlobalDependencySupport.collectDependencies(
|
||||||
|
surface.initializer(),
|
||||||
|
surface.ownerModuleId(),
|
||||||
|
globalNamesByModule.getOrDefault(surface.ownerModuleId(), new LinkedHashSet<>()),
|
||||||
|
importedByLocalName));
|
||||||
|
}
|
||||||
|
|
||||||
|
final var analysis = new DependencyGraphAnaliser<PbsGlobalDependencySupport.GlobalRef>(
|
||||||
|
Comparator.comparingInt((PbsGlobalDependencySupport.GlobalRef ref) -> ref.moduleId().isNone() ? Integer.MAX_VALUE : ref.moduleId().getIndex())
|
||||||
|
.thenComparing(PbsGlobalDependencySupport.GlobalRef::globalName))
|
||||||
|
.analyse(graph);
|
||||||
|
return new Analysis(analysis, spansByRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate(
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final ModuleId currentModuleId,
|
||||||
|
final p.studio.utilities.structures.ReadOnlyList<PbsFrontendCompiler.ImportedGlobalSurface> importedGlobals,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var analysis = analyse(ast, currentModuleId, importedGlobals);
|
||||||
|
for (final var cycle : analysis.graphAnalysis().cycleComponents()) {
|
||||||
|
for (final var ref : cycle) {
|
||||||
|
final var span = analysis.spansByRef().get(ref);
|
||||||
|
if (span == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(
|
||||||
|
diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_GLOBAL_CYCLIC_DEPENDENCY.name(),
|
||||||
|
"Cyclic global dependency detected involving '%s'".formatted(ref.globalName()),
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedHashMap<String, PbsAst.GlobalDecl> collectLocalGlobalDecls(final PbsAst.File ast) {
|
||||||
|
final var globals = new LinkedHashMap<String, PbsAst.GlobalDecl>();
|
||||||
|
for (final var topDecl : ast.topDecls()) {
|
||||||
|
if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
|
||||||
|
globals.putIfAbsent(globalDecl.name(), globalDecl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return globals;
|
||||||
|
}
|
||||||
|
|
||||||
|
record Analysis(
|
||||||
|
DependencyGraphAnaliser.Analysis<PbsGlobalDependencySupport.GlobalRef> graphAnalysis,
|
||||||
|
Map<PbsGlobalDependencySupport.GlobalRef, Span> spansByRef) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,6 +20,7 @@ public enum PbsSemanticsErrors {
|
|||||||
E_SEM_MISSING_GLOBAL_TYPE_ANNOTATION,
|
E_SEM_MISSING_GLOBAL_TYPE_ANNOTATION,
|
||||||
E_SEM_MISSING_GLOBAL_INITIALIZER,
|
E_SEM_MISSING_GLOBAL_INITIALIZER,
|
||||||
E_SEM_GLOBAL_UNSUPPORTED_INITIALIZER,
|
E_SEM_GLOBAL_UNSUPPORTED_INITIALIZER,
|
||||||
|
E_SEM_GLOBAL_CYCLIC_DEPENDENCY,
|
||||||
E_SEM_CONST_NON_CONSTANT_INITIALIZER,
|
E_SEM_CONST_NON_CONSTANT_INITIALIZER,
|
||||||
E_SEM_CONST_INITIALIZER_TYPE_MISMATCH,
|
E_SEM_CONST_INITIALIZER_TYPE_MISMATCH,
|
||||||
E_SEM_CONST_CYCLIC_DEPENDENCY,
|
E_SEM_CONST_CYCLIC_DEPENDENCY,
|
||||||
|
|||||||
@ -76,6 +76,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
nameTable,
|
nameTable,
|
||||||
importedSemanticContext.supplementalTopDecls(),
|
importedSemanticContext.supplementalTopDecls(),
|
||||||
importedSemanticContext.importedCallables(),
|
importedSemanticContext.importedCallables(),
|
||||||
|
importedSemanticContext.importedGlobals(),
|
||||||
importedSemanticContext.importedReservedMetadata());
|
importedSemanticContext.importedReservedMetadata());
|
||||||
if (diagnostics.errorCount() > compileErrorBaseline) {
|
if (diagnostics.errorCount() > compileErrorBaseline) {
|
||||||
failedModuleIds.add(parsedSource.moduleId());
|
failedModuleIds.add(parsedSource.moduleId());
|
||||||
|
|||||||
@ -8,8 +8,13 @@ import p.studio.utilities.structures.ReadOnlyList;
|
|||||||
record PbsImportedSemanticContext(
|
record PbsImportedSemanticContext(
|
||||||
ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||||
ReadOnlyList<PbsFrontendCompiler.ImportedCallableSurface> importedCallables,
|
ReadOnlyList<PbsFrontendCompiler.ImportedCallableSurface> importedCallables,
|
||||||
|
ReadOnlyList<PbsFrontendCompiler.ImportedGlobalSurface> importedGlobals,
|
||||||
IRReservedMetadata importedReservedMetadata) {
|
IRReservedMetadata importedReservedMetadata) {
|
||||||
static PbsImportedSemanticContext empty() {
|
static PbsImportedSemanticContext empty() {
|
||||||
return new PbsImportedSemanticContext(ReadOnlyList.empty(), ReadOnlyList.empty(), IRReservedMetadata.empty());
|
return new PbsImportedSemanticContext(
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
IRReservedMetadata.empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import p.studio.compiler.models.IRReservedMetadata;
|
|||||||
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||||
import p.studio.compiler.pbs.PbsReservedMetadataExtractor;
|
import p.studio.compiler.pbs.PbsReservedMetadataExtractor;
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsGlobalDependencySupport;
|
||||||
import p.studio.compiler.source.identifiers.FileId;
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
import p.studio.compiler.source.identifiers.ModuleId;
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
import p.studio.compiler.source.tables.ModuleTable;
|
import p.studio.compiler.source.tables.ModuleTable;
|
||||||
@ -46,14 +47,18 @@ final class PbsImportedSemanticContextService {
|
|||||||
for (final var entry : sourcesByModule.entrySet()) {
|
for (final var entry : sourcesByModule.entrySet()) {
|
||||||
topDeclsByNameByModule.put(entry.getKey(), indexTopDeclsByName(entry.getValue()));
|
topDeclsByNameByModule.put(entry.getKey(), indexTopDeclsByName(entry.getValue()));
|
||||||
}
|
}
|
||||||
|
final var globalInfosByRef = indexGlobalInfos(sourcesByModule, topDeclsByNameByModule, moduleTable);
|
||||||
|
|
||||||
final Map<FileId, PbsImportedSemanticContext> contexts = new HashMap<>();
|
final Map<FileId, PbsImportedSemanticContext> contexts = new HashMap<>();
|
||||||
for (final var parsedSource : parsedSourceFiles) {
|
for (final var parsedSource : parsedSourceFiles) {
|
||||||
final var supplementalTopDecls = new ArrayList<PbsAst.TopDecl>();
|
final var supplementalTopDecls = new ArrayList<PbsAst.TopDecl>();
|
||||||
final var importedCallables = new ArrayList<PbsFrontendCompiler.ImportedCallableSurface>();
|
final var importedCallables = new ArrayList<PbsFrontendCompiler.ImportedCallableSurface>();
|
||||||
|
final var importedGlobals = new ArrayList<PbsFrontendCompiler.ImportedGlobalSurface>();
|
||||||
final var importedCallableKeys = new HashSet<String>();
|
final var importedCallableKeys = new HashSet<String>();
|
||||||
|
final var importedGlobalKeys = new HashSet<String>();
|
||||||
final var supplementalKeys = new HashSet<String>();
|
final var supplementalKeys = new HashSet<String>();
|
||||||
var importedReservedMetadata = IRReservedMetadata.empty();
|
var importedReservedMetadata = IRReservedMetadata.empty();
|
||||||
|
final var directlyVisibleGlobalRefs = new LinkedHashMap<String, PbsGlobalDependencySupport.GlobalRef>();
|
||||||
|
|
||||||
for (final var importDecl : parsedSource.ast().imports()) {
|
for (final var importDecl : parsedSource.ast().imports()) {
|
||||||
final var moduleRef = importDecl.moduleRef();
|
final var moduleRef = importDecl.moduleRef();
|
||||||
@ -102,14 +107,43 @@ final class PbsImportedSemanticContextService {
|
|||||||
functionDecl.parameters().size(),
|
functionDecl.parameters().size(),
|
||||||
returnSlotsFor(functionDecl),
|
returnSlotsFor(functionDecl),
|
||||||
frontendCompiler.callableShapeSurfaceOf(functionDecl)));
|
frontendCompiler.callableShapeSurfaceOf(functionDecl)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
|
||||||
|
directlyVisibleGlobalRefs.putIfAbsent(
|
||||||
|
localName,
|
||||||
|
new PbsGlobalDependencySupport.GlobalRef(importedModuleId, globalDecl.name()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final var reachableImportedGlobals = collectReachableImportedGlobals(directlyVisibleGlobalRefs, globalInfosByRef);
|
||||||
|
for (final var entry : directlyVisibleGlobalRefs.entrySet()) {
|
||||||
|
final var info = globalInfosByRef.get(entry.getValue());
|
||||||
|
if (info == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
appendImportedGlobal(
|
||||||
|
importedGlobals,
|
||||||
|
importedGlobalKeys,
|
||||||
|
toImportedGlobalSurface(info, entry.getKey(), true));
|
||||||
|
}
|
||||||
|
for (final var reachableRef : reachableImportedGlobals) {
|
||||||
|
final var info = globalInfosByRef.get(reachableRef);
|
||||||
|
if (info == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
appendImportedGlobal(
|
||||||
|
importedGlobals,
|
||||||
|
importedGlobalKeys,
|
||||||
|
toImportedGlobalSurface(info, info.globalDecl().name(), false));
|
||||||
|
}
|
||||||
|
|
||||||
contexts.put(parsedSource.fileId(), new PbsImportedSemanticContext(
|
contexts.put(parsedSource.fileId(), new PbsImportedSemanticContext(
|
||||||
ReadOnlyList.wrap(supplementalTopDecls),
|
ReadOnlyList.wrap(supplementalTopDecls),
|
||||||
ReadOnlyList.wrap(importedCallables),
|
ReadOnlyList.wrap(importedCallables),
|
||||||
|
ReadOnlyList.wrap(importedGlobals),
|
||||||
importedReservedMetadata));
|
importedReservedMetadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,6 +246,10 @@ final class PbsImportedSemanticContextService {
|
|||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) {
|
if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) {
|
||||||
collectReferencedTypeNames(constDecl.explicitType(), sink);
|
collectReferencedTypeNames(constDecl.explicitType(), sink);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.GlobalDecl globalDecl && globalDecl.explicitType() != null) {
|
||||||
|
collectReferencedTypeNames(globalDecl.explicitType(), sink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,6 +357,22 @@ final class PbsImportedSemanticContextService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void appendImportedGlobal(
|
||||||
|
final ArrayList<PbsFrontendCompiler.ImportedGlobalSurface> importedGlobals,
|
||||||
|
final Set<String> importedGlobalKeys,
|
||||||
|
final PbsFrontendCompiler.ImportedGlobalSurface importedGlobalSurface) {
|
||||||
|
final var globalKey = importedGlobalSurface.ownerModuleId().getIndex()
|
||||||
|
+ "#"
|
||||||
|
+ importedGlobalSurface.globalName()
|
||||||
|
+ "#"
|
||||||
|
+ importedGlobalSurface.localName()
|
||||||
|
+ "#"
|
||||||
|
+ importedGlobalSurface.directlyVisible();
|
||||||
|
if (importedGlobalKeys.add(globalKey)) {
|
||||||
|
importedGlobals.add(importedGlobalSurface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String topDeclKey(final PbsAst.TopDecl topDecl) {
|
private String topDeclKey(final PbsAst.TopDecl topDecl) {
|
||||||
final var declName = topDeclName(topDecl);
|
final var declName = topDeclName(topDecl);
|
||||||
if (declName == null || declName.isBlank()) {
|
if (declName == null || declName.isBlank()) {
|
||||||
@ -337,6 +391,9 @@ final class PbsImportedSemanticContextService {
|
|||||||
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||||
return constDecl.name();
|
return constDecl.name();
|
||||||
}
|
}
|
||||||
|
if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
|
||||||
|
return globalDecl.name();
|
||||||
|
}
|
||||||
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
return serviceDecl.name();
|
return serviceDecl.name();
|
||||||
}
|
}
|
||||||
@ -400,4 +457,111 @@ final class PbsImportedSemanticContextService {
|
|||||||
}
|
}
|
||||||
return importItem.alias();
|
return importItem.alias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<PbsGlobalDependencySupport.GlobalRef, GlobalInfo> indexGlobalInfos(
|
||||||
|
final Map<ModuleId, ArrayList<PbsParsedSourceFile>> sourcesByModule,
|
||||||
|
final Map<ModuleId, Map<String, ArrayList<PbsAst.TopDecl>>> topDeclsByNameByModule,
|
||||||
|
final ModuleTable moduleTable) {
|
||||||
|
final Map<PbsGlobalDependencySupport.GlobalRef, GlobalInfo> globalsByRef = new LinkedHashMap<>();
|
||||||
|
for (final var entry : sourcesByModule.entrySet()) {
|
||||||
|
final var moduleId = entry.getKey();
|
||||||
|
for (final var source : entry.getValue()) {
|
||||||
|
final var importedGlobalsByLocalName = directImportedGlobalsFor(source.ast(), topDeclsByNameByModule, moduleTable);
|
||||||
|
for (final var topDecl : source.ast().topDecls()) {
|
||||||
|
if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
|
||||||
|
globalsByRef.putIfAbsent(
|
||||||
|
new PbsGlobalDependencySupport.GlobalRef(moduleId, globalDecl.name()),
|
||||||
|
new GlobalInfo(moduleId, globalDecl, importedGlobalsByLocalName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return globalsByRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, PbsGlobalDependencySupport.GlobalRef> directImportedGlobalsFor(
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final Map<ModuleId, Map<String, ArrayList<PbsAst.TopDecl>>> topDeclsByNameByModule,
|
||||||
|
final ModuleTable moduleTable) {
|
||||||
|
final Map<String, PbsGlobalDependencySupport.GlobalRef> importedGlobalsByLocalName = new LinkedHashMap<>();
|
||||||
|
for (final var importDecl : ast.imports()) {
|
||||||
|
final var importedModuleId = moduleTable.register(
|
||||||
|
new p.studio.compiler.source.tables.ModuleReference(
|
||||||
|
importDecl.moduleRef().project(),
|
||||||
|
importDecl.moduleRef().pathSegments()));
|
||||||
|
final var importedTopDeclsByName = topDeclsByNameByModule.get(importedModuleId);
|
||||||
|
if (importedTopDeclsByName == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (final var importItem : importDecl.items()) {
|
||||||
|
final var candidates = importedTopDeclsByName.getOrDefault(importItem.name(), new ArrayList<>());
|
||||||
|
for (final var topDecl : candidates) {
|
||||||
|
if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
|
||||||
|
importedGlobalsByLocalName.putIfAbsent(
|
||||||
|
importItemLocalName(importItem),
|
||||||
|
new PbsGlobalDependencySupport.GlobalRef(importedModuleId, globalDecl.name()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return importedGlobalsByLocalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedHashSet<PbsGlobalDependencySupport.GlobalRef> collectReachableImportedGlobals(
|
||||||
|
final Map<String, PbsGlobalDependencySupport.GlobalRef> directlyVisibleGlobalRefs,
|
||||||
|
final Map<PbsGlobalDependencySupport.GlobalRef, GlobalInfo> globalInfosByRef) {
|
||||||
|
final var visited = new LinkedHashSet<PbsGlobalDependencySupport.GlobalRef>();
|
||||||
|
final var pending = new ArrayDeque<PbsGlobalDependencySupport.GlobalRef>(directlyVisibleGlobalRefs.values());
|
||||||
|
while (!pending.isEmpty()) {
|
||||||
|
final var current = pending.removeFirst();
|
||||||
|
if (!visited.add(current)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final var info = globalInfosByRef.get(current);
|
||||||
|
if (info == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final var globalsInSameModule = new LinkedHashSet<String>();
|
||||||
|
for (final var entry : globalInfosByRef.entrySet()) {
|
||||||
|
if (entry.getKey().moduleId().equals(info.moduleId())) {
|
||||||
|
globalsInSameModule.add(entry.getKey().globalName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pending.addAll(PbsGlobalDependencySupport.collectDependencies(
|
||||||
|
info.globalDecl().initializer(),
|
||||||
|
info.moduleId(),
|
||||||
|
globalsInSameModule,
|
||||||
|
info.importedGlobalsByLocalName()));
|
||||||
|
}
|
||||||
|
return visited;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsFrontendCompiler.ImportedGlobalSurface toImportedGlobalSurface(
|
||||||
|
final GlobalInfo info,
|
||||||
|
final String localName,
|
||||||
|
final boolean directlyVisible) {
|
||||||
|
final var importedDependencies = new ArrayList<PbsFrontendCompiler.ImportedGlobalDependency>();
|
||||||
|
for (final var entry : info.importedGlobalsByLocalName().entrySet()) {
|
||||||
|
importedDependencies.add(new PbsFrontendCompiler.ImportedGlobalDependency(
|
||||||
|
entry.getKey(),
|
||||||
|
entry.getValue().moduleId(),
|
||||||
|
entry.getValue().globalName()));
|
||||||
|
}
|
||||||
|
return new PbsFrontendCompiler.ImportedGlobalSurface(
|
||||||
|
info.moduleId(),
|
||||||
|
info.globalDecl().name(),
|
||||||
|
localName,
|
||||||
|
info.globalDecl().explicitType(),
|
||||||
|
info.globalDecl().initializer(),
|
||||||
|
ReadOnlyList.wrap(importedDependencies),
|
||||||
|
info.globalDecl().span(),
|
||||||
|
directlyVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record GlobalInfo(
|
||||||
|
ModuleId moduleId,
|
||||||
|
PbsAst.GlobalDecl globalDecl,
|
||||||
|
Map<String, PbsGlobalDependencySupport.GlobalRef> importedGlobalsByLocalName) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,82 @@
|
|||||||
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||||
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.pbs.lexer.PbsLexer;
|
||||||
|
import p.studio.compiler.pbs.parser.PbsParser;
|
||||||
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class PbsGlobalSemanticsValidatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldProduceDeterministicGlobalOrderIndependentOfSourceOrder() {
|
||||||
|
final var firstAst = parse("""
|
||||||
|
declare global B: int = A + 1;
|
||||||
|
declare global C: int = B + 1;
|
||||||
|
declare global A: int = 1;
|
||||||
|
""");
|
||||||
|
final var secondAst = parse("""
|
||||||
|
declare global C: int = B + 1;
|
||||||
|
declare global A: int = 1;
|
||||||
|
declare global B: int = A + 1;
|
||||||
|
""");
|
||||||
|
|
||||||
|
final var validator = new PbsGlobalSemanticsValidator();
|
||||||
|
final var firstOrder = validator.analyse(firstAst, ModuleId.none(), ReadOnlyList.empty())
|
||||||
|
.graphAnalysis()
|
||||||
|
.traversalOrder()
|
||||||
|
.asList()
|
||||||
|
.stream()
|
||||||
|
.map(PbsGlobalDependencySupport.GlobalRef::globalName)
|
||||||
|
.toList();
|
||||||
|
final var secondOrder = validator.analyse(secondAst, ModuleId.none(), ReadOnlyList.empty())
|
||||||
|
.graphAnalysis()
|
||||||
|
.traversalOrder()
|
||||||
|
.asList()
|
||||||
|
.stream()
|
||||||
|
.map(PbsGlobalDependencySupport.GlobalRef::globalName)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
assertEquals(List.of("A", "B", "C"), firstOrder);
|
||||||
|
assertEquals(firstOrder, secondOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectCyclicGlobalDependencies() {
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), """
|
||||||
|
declare global A: int = B + 1;
|
||||||
|
declare global B: int = C + 1;
|
||||||
|
declare global C: int = A + 1;
|
||||||
|
""", diagnostics);
|
||||||
|
|
||||||
|
final var cycleCount = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_GLOBAL_CYCLIC_DEPENDENCY.name()))
|
||||||
|
.count();
|
||||||
|
assertEquals(3, cycleCount);
|
||||||
|
assertFalse(diagnostics.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.File parse(final String source) {
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var fileId = new FileId(0);
|
||||||
|
final var ast = PbsParser.parse(
|
||||||
|
PbsLexer.lex(source, fileId, diagnostics),
|
||||||
|
fileId,
|
||||||
|
diagnostics,
|
||||||
|
PbsParser.ParseMode.ORDINARY);
|
||||||
|
assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1168,6 +1168,65 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
assertTrue(intrinsicCalls.contains("input.touch.y"));
|
assertTrue(intrinsicCalls.contains("input.touch.y"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReportInterModuleGlobalCycleThroughImportedAlias() throws IOException {
|
||||||
|
final var projectRoot = tempDir.resolve("project-global-cycle-alias");
|
||||||
|
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, """
|
||||||
|
import { B as ImportedB } from @app:b;
|
||||||
|
|
||||||
|
declare global A: int = ImportedB + 1;
|
||||||
|
""");
|
||||||
|
Files.writeString(barrelA, "pub global A;");
|
||||||
|
|
||||||
|
final var sourceB = moduleBPath.resolve("source.pbs");
|
||||||
|
final var barrelB = moduleBPath.resolve("mod.barrel");
|
||||||
|
Files.writeString(sourceB, """
|
||||||
|
import { A } from @app:a;
|
||||||
|
|
||||||
|
declare global B: int = A + 1;
|
||||||
|
""");
|
||||||
|
Files.writeString(barrelB, "pub global B;");
|
||||||
|
|
||||||
|
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 cycleCount = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_GLOBAL_CYCLIC_DEPENDENCY.name()))
|
||||||
|
.count();
|
||||||
|
assertEquals(2, cycleCount, diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
||||||
|
}
|
||||||
|
|
||||||
private void registerFile(
|
private void registerFile(
|
||||||
final ProjectId projectId,
|
final ProjectId projectId,
|
||||||
final Path projectRoot,
|
final Path projectRoot,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user