implements PR013

This commit is contained in:
bQUARKz 2026-03-05 18:47:16 +00:00
parent 7dfaf6b49b
commit c0bccba092
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 75 additions and 4 deletions

View File

@ -8,5 +8,7 @@ public enum PbsLinkErrors {
E_LINK_DUPLICATE_BARREL_ENTRY,
E_LINK_UNRESOLVED_BARREL_ENTRY,
E_LINK_AMBIGUOUS_BARREL_ENTRY,
E_LINK_IMPORT_MODULE_NOT_FOUND,
E_LINK_IMPORT_SYMBOL_UNRESOLVED,
E_LINK_IMPORT_SYMBOL_NOT_PUBLIC
}

View File

@ -74,6 +74,7 @@ public final class PbsModuleVisibilityValidator {
final var barrel = module.barrelFiles().getFirst();
final var declarations = collectDeclarations(module, nameTable);
exports.declaredNameIds.addAll(declarations.declaredNameIds);
final Map<NonFunctionSymbolKey, Span> seenNonFunctionEntries = new HashMap<>();
final Map<FunctionSymbolKey, Span> seenFunctionEntries = new HashMap<>();
@ -179,20 +180,36 @@ public final class PbsModuleVisibilityValidator {
final DiagnosticSink diagnostics) {
for (final var source : module.sourceFiles()) {
for (final var importDecl : source.ast().imports()) {
if (importDecl.items().isEmpty()) {
continue;
}
final var targetModule = new ModuleRefKey(
importDecl.moduleRef().project(),
importDecl.moduleRef().pathSegments());
final var targetExports = exportsByModule.get(targetModule);
if (targetExports == null) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name(),
"Import target module %s was not found".formatted(displayModule(targetModule)),
importDecl.moduleRef().span());
continue;
}
if (importDecl.items().isEmpty()) {
continue;
}
for (final var importItem : importDecl.items()) {
final var importedNameId = nameTable.register(importItem.name());
if (targetExports.publicNameIds.contains(importedNameId)) {
continue;
}
if (!targetExports.declaredNameIds.contains(importedNameId)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsLinkErrors.E_LINK_IMPORT_SYMBOL_UNRESOLVED.name(),
"Symbol '%s' does not exist in module %s".formatted(
importItem.name(),
displayModule(targetModule)),
importItem.span());
continue;
}
if (!targetExports.publicNameIds.contains(importedNameId)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name(),
@ -224,6 +241,7 @@ public final class PbsModuleVisibilityValidator {
declarations.functionsBySignature
.computeIfAbsent(key, ignored -> new ArrayList<>())
.add(functionDecl.span());
declarations.declaredNameIds.add(key.nameId());
continue;
}
@ -259,6 +277,7 @@ public final class PbsModuleVisibilityValidator {
declarations.nonFunctionsByKindAndName
.computeIfAbsent(key, ignored -> new ArrayList<>())
.add(span);
declarations.declaredNameIds.add(key.nameId());
}
private FunctionSymbolKey functionKey(
@ -467,9 +486,11 @@ public final class PbsModuleVisibilityValidator {
private static final class ModuleDeclarations {
private final Map<NonFunctionSymbolKey, List<Span>> nonFunctionsByKindAndName = new HashMap<>();
private final Map<FunctionSymbolKey, List<Span>> functionsBySignature = new HashMap<>();
private final Set<NameId> declaredNameIds = new HashSet<>();
}
private static final class ModuleExports {
private final Set<NameId> declaredNameIds = new HashSet<>();
private final Set<NameId> publicNameIds = new HashSet<>();
}
}

View File

@ -149,6 +149,54 @@ class PbsModuleVisibilityTest {
assertTrue(importVisibilityDiagnostics.getFirst().getMessage().contains("secret"));
}
@Test
void shouldRejectImportFromMissingModule() {
final var diagnostics = DiagnosticSink.empty();
final var nextFileId = new AtomicInteger(0);
final var moduleApp = module("core", "app", List.of(
"""
import { open } from @core:missing;
fn use() -> int { return 1; }
"""
), """
pub fn use() -> int;
""", nextFileId, diagnostics);
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(moduleApp)), diagnostics);
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name())));
}
@Test
void shouldRejectImportOfUnknownSymbol() {
final var diagnostics = DiagnosticSink.empty();
final var nextFileId = new AtomicInteger(0);
final var moduleMath = module("core", "math", List.of(
"""
fn open() -> int { return 2; }
"""
), """
pub fn open() -> int;
""", nextFileId, diagnostics);
final var moduleApp = module("core", "app", List.of(
"""
import { missing } from @core:math;
fn use() -> int { return 1; }
"""
), """
pub fn use() -> int;
""", nextFileId, diagnostics);
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(moduleMath, moduleApp)), diagnostics);
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_UNRESOLVED.name())));
}
@Test
void shouldResolveBarrelFunctionWithResultReturnSurface() {
final var diagnostics = DiagnosticSink.empty();