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,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.empty(),
|
||||
IRReservedMetadata.empty());
|
||||
}
|
||||
|
||||
@ -104,6 +105,7 @@ public final class PbsFrontendCompiler {
|
||||
final NameTable nameTable,
|
||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||
final ReadOnlyList<ImportedCallableSurface> importedCallables,
|
||||
final ReadOnlyList<ImportedGlobalSurface> importedGlobals,
|
||||
final IRReservedMetadata importedReservedMetadata) {
|
||||
final var effectiveModuleId = moduleId == null ? ModuleId.none() : moduleId;
|
||||
final var effectiveModulePool = modulePool == null ? ReadOnlyList.<ModuleReference>empty() : modulePool;
|
||||
@ -114,11 +116,19 @@ public final class PbsFrontendCompiler {
|
||||
final var effectiveImportedCallables = importedCallables == null
|
||||
? ReadOnlyList.<ImportedCallableSurface>empty()
|
||||
: importedCallables;
|
||||
final var effectiveImportedGlobals = importedGlobals == null
|
||||
? ReadOnlyList.<ImportedGlobalSurface>empty()
|
||||
: importedGlobals;
|
||||
final var effectiveImportedReservedMetadata = importedReservedMetadata == null
|
||||
? IRReservedMetadata.empty()
|
||||
: importedReservedMetadata;
|
||||
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);
|
||||
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
||||
return IRBackendFile.empty(fileId);
|
||||
@ -233,4 +243,28 @@ public final class PbsFrontendCompiler {
|
||||
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;
|
||||
|
||||
import p.studio.compiler.models.SourceKind;
|
||||
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.compiler.source.tables.NameTable;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
@ -25,6 +27,7 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
|
||||
private final NameTable nameTable;
|
||||
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
||||
private final PbsGlobalSemanticsValidator globalSemanticsValidator = new PbsGlobalSemanticsValidator();
|
||||
|
||||
public PbsDeclarationSemanticsValidator(final NameTable nameTable) {
|
||||
this.nameTable = nameTable == null ? new NameTable() : nameTable;
|
||||
@ -38,6 +41,15 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
final PbsAst.File ast,
|
||||
final SourceKind sourceKind,
|
||||
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 rules = new PbsDeclarationRuleValidator(nameTable, diagnostics);
|
||||
final var interfaceModule = sourceKind == SourceKind.SDK_INTERFACE;
|
||||
@ -153,6 +165,7 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
}
|
||||
|
||||
constSemanticsValidator.validate(ast, diagnostics);
|
||||
globalSemanticsValidator.validate(ast, currentModuleId, importedGlobals, diagnostics);
|
||||
if (interfaceModule) {
|
||||
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_INITIALIZER,
|
||||
E_SEM_GLOBAL_UNSUPPORTED_INITIALIZER,
|
||||
E_SEM_GLOBAL_CYCLIC_DEPENDENCY,
|
||||
E_SEM_CONST_NON_CONSTANT_INITIALIZER,
|
||||
E_SEM_CONST_INITIALIZER_TYPE_MISMATCH,
|
||||
E_SEM_CONST_CYCLIC_DEPENDENCY,
|
||||
|
||||
@ -76,6 +76,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
nameTable,
|
||||
importedSemanticContext.supplementalTopDecls(),
|
||||
importedSemanticContext.importedCallables(),
|
||||
importedSemanticContext.importedGlobals(),
|
||||
importedSemanticContext.importedReservedMetadata());
|
||||
if (diagnostics.errorCount() > compileErrorBaseline) {
|
||||
failedModuleIds.add(parsedSource.moduleId());
|
||||
|
||||
@ -8,8 +8,13 @@ import p.studio.utilities.structures.ReadOnlyList;
|
||||
record PbsImportedSemanticContext(
|
||||
ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||
ReadOnlyList<PbsFrontendCompiler.ImportedCallableSurface> importedCallables,
|
||||
ReadOnlyList<PbsFrontendCompiler.ImportedGlobalSurface> importedGlobals,
|
||||
IRReservedMetadata importedReservedMetadata) {
|
||||
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.PbsReservedMetadataExtractor;
|
||||
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.ModuleId;
|
||||
import p.studio.compiler.source.tables.ModuleTable;
|
||||
@ -46,14 +47,18 @@ final class PbsImportedSemanticContextService {
|
||||
for (final var entry : sourcesByModule.entrySet()) {
|
||||
topDeclsByNameByModule.put(entry.getKey(), indexTopDeclsByName(entry.getValue()));
|
||||
}
|
||||
final var globalInfosByRef = indexGlobalInfos(sourcesByModule, topDeclsByNameByModule, moduleTable);
|
||||
|
||||
final Map<FileId, PbsImportedSemanticContext> contexts = new HashMap<>();
|
||||
for (final var parsedSource : parsedSourceFiles) {
|
||||
final var supplementalTopDecls = new ArrayList<PbsAst.TopDecl>();
|
||||
final var importedCallables = new ArrayList<PbsFrontendCompiler.ImportedCallableSurface>();
|
||||
final var importedGlobals = new ArrayList<PbsFrontendCompiler.ImportedGlobalSurface>();
|
||||
final var importedCallableKeys = new HashSet<String>();
|
||||
final var importedGlobalKeys = new HashSet<String>();
|
||||
final var supplementalKeys = new HashSet<String>();
|
||||
var importedReservedMetadata = IRReservedMetadata.empty();
|
||||
final var directlyVisibleGlobalRefs = new LinkedHashMap<String, PbsGlobalDependencySupport.GlobalRef>();
|
||||
|
||||
for (final var importDecl : parsedSource.ast().imports()) {
|
||||
final var moduleRef = importDecl.moduleRef();
|
||||
@ -102,14 +107,43 @@ final class PbsImportedSemanticContextService {
|
||||
functionDecl.parameters().size(),
|
||||
returnSlotsFor(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(
|
||||
ReadOnlyList.wrap(supplementalTopDecls),
|
||||
ReadOnlyList.wrap(importedCallables),
|
||||
ReadOnlyList.wrap(importedGlobals),
|
||||
importedReservedMetadata));
|
||||
}
|
||||
|
||||
@ -212,6 +246,10 @@ final class PbsImportedSemanticContextService {
|
||||
}
|
||||
if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) {
|
||||
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) {
|
||||
final var declName = topDeclName(topDecl);
|
||||
if (declName == null || declName.isBlank()) {
|
||||
@ -337,6 +391,9 @@ final class PbsImportedSemanticContextService {
|
||||
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||
return constDecl.name();
|
||||
}
|
||||
if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
|
||||
return globalDecl.name();
|
||||
}
|
||||
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||
return serviceDecl.name();
|
||||
}
|
||||
@ -400,4 +457,111 @@ final class PbsImportedSemanticContextService {
|
||||
}
|
||||
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"));
|
||||
}
|
||||
|
||||
@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(
|
||||
final ProjectId projectId,
|
||||
final Path projectRoot,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user