implements PR-08.1

This commit is contained in:
bQUARKz 2026-03-09 14:06:10 +00:00
parent e11cfee9a4
commit 40188b67ab
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 250 additions and 10 deletions

View File

@ -186,6 +186,7 @@ public final class PbsFrontendCompiler {
final var executableLowering = lowerExecutableFunctions(
fileId,
ast,
effectiveSupplementalTopDecls,
moduleKey,
effectiveReservedMetadata,
effectiveNameTable,
@ -219,6 +220,7 @@ public final class PbsFrontendCompiler {
private ExecutableLoweringResult lowerExecutableFunctions(
final FileId fileId,
final PbsAst.File ast,
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
final String moduleKey,
final IRReservedMetadata reservedMetadata,
final NameTable nameTable,
@ -231,12 +233,77 @@ public final class PbsFrontendCompiler {
.computeIfAbsent(nameTable.register(hostBinding.sourceMethodName()), ignored -> new ArrayList<>())
.add(hostBinding);
}
final var intrinsicByMethodName = new HashMap<NameId, List<IRReservedMetadata.IntrinsicSurface>>();
final var builtinCanonicalBySourceType = new HashMap<String, String>();
for (final var builtinType : reservedMetadata.builtinTypeSurfaces()) {
builtinCanonicalBySourceType.putIfAbsent(builtinType.sourceTypeName(), builtinType.canonicalTypeName());
}
final var builtinConstOwnerByNameId = new HashMap<NameId, String>();
for (final var builtinConst : reservedMetadata.builtinConstSurfaces()) {
if (builtinConst.sourceConstName().isBlank() || builtinConst.canonicalTarget().isBlank()) {
continue;
}
builtinConstOwnerByNameId.put(nameTable.register(builtinConst.sourceConstName()), builtinConst.canonicalTarget());
}
final var builtinSignatureByOwnerAndMethod = new HashMap<IntrinsicOwnerMethodKey, PbsAst.FunctionSignature>();
final var topDeclsForIntrinsicInference = new ArrayList<PbsAst.TopDecl>(ast.topDecls().size() + supplementalTopDecls.size());
topDeclsForIntrinsicInference.addAll(ast.topDecls().asList());
topDeclsForIntrinsicInference.addAll(supplementalTopDecls.asList());
for (final var topDecl : topDeclsForIntrinsicInference) {
if (!(topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl)) {
continue;
}
final var ownerCanonical = builtinCanonicalBySourceType.getOrDefault(builtinTypeDecl.name(), "");
if (ownerCanonical.isBlank()) {
continue;
}
for (final var signature : builtinTypeDecl.signatures()) {
builtinSignatureByOwnerAndMethod.putIfAbsent(
new IntrinsicOwnerMethodKey(ownerCanonical, signature.name()),
signature);
}
}
final var intrinsicByOwnerAndMethod = new HashMap<IntrinsicOwnerMethodKey, List<IRReservedMetadata.IntrinsicSurface>>();
final var intrinsicReturnOwnerByCanonical = new HashMap<IntrinsicCanonicalKey, String>();
for (final var builtinType : reservedMetadata.builtinTypeSurfaces()) {
final var ownerCanonical = builtinType.canonicalTypeName();
for (final var intrinsicSurface : builtinType.intrinsics()) {
intrinsicByMethodName
.computeIfAbsent(nameTable.register(intrinsicSurface.sourceMethodName()), ignored -> new ArrayList<>())
intrinsicByOwnerAndMethod
.computeIfAbsent(
new IntrinsicOwnerMethodKey(ownerCanonical, intrinsicSurface.sourceMethodName()),
ignored -> new ArrayList<>())
.add(intrinsicSurface);
final var signature = builtinSignatureByOwnerAndMethod.get(
new IntrinsicOwnerMethodKey(ownerCanonical, intrinsicSurface.sourceMethodName()));
if (signature == null) {
continue;
}
final var returnOwner = inferIntrinsicReturnOwner(
signature,
ownerCanonical,
builtinCanonicalBySourceType);
if (returnOwner.isBlank()) {
continue;
}
final var key = new IntrinsicCanonicalKey(
intrinsicSurface.canonicalName(),
intrinsicSurface.canonicalVersion());
final var previous = intrinsicReturnOwnerByCanonical.putIfAbsent(key, returnOwner);
if (previous != null && !previous.equals(returnOwner)) {
diagnostics.error(
DiagnosticPhase.STATIC_SEMANTICS,
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
Map.of(),
"inconsistent intrinsic return owner metadata for %s@%d: %s vs %s".formatted(
intrinsicSurface.canonicalName(),
intrinsicSurface.canonicalVersion(),
previous,
returnOwner),
intrinsicSurface.span());
}
}
}
final var callableIdTable = new CallableTable();
@ -296,7 +363,9 @@ public final class PbsFrontendCompiler {
diagnostics,
nameTable,
hostByMethodName,
intrinsicByMethodName,
intrinsicByOwnerAndMethod,
intrinsicReturnOwnerByCanonical,
builtinConstOwnerByNameId,
callableIdsByNameAndArity,
returnSlotsByCallableId,
intrinsicIdTable,
@ -458,6 +527,25 @@ public final class PbsFrontendCompiler {
};
}
private String inferIntrinsicReturnOwner(
final PbsAst.FunctionSignature signature,
final String intrinsicOwnerCanonical,
final Map<String, String> builtinCanonicalBySourceType) {
if (signature.returnKind() == PbsAst.ReturnKind.INFERRED_UNIT
|| signature.returnKind() == PbsAst.ReturnKind.EXPLICIT_UNIT) {
return "";
}
final var returnType = unwrapGroup(signature.returnType());
if (returnType == null) {
return "";
}
return switch (returnType.kind()) {
case SELF -> intrinsicOwnerCanonical == null ? "" : intrinsicOwnerCanonical;
case SIMPLE -> builtinCanonicalBySourceType.getOrDefault(returnType.name(), "");
default -> "";
};
}
private String typeSurfaceKey(final PbsAst.TypeRef typeRef) {
final var unwrapped = unwrapGroup(typeRef);
if (unwrapped == null) {
@ -758,7 +846,7 @@ public final class PbsFrontendCompiler {
}
final var hostCandidates = context.hostByMethodName().getOrDefault(calleeIdentity.memberNameId(), List.of());
final var intrinsicCandidates = context.intrinsicByMethodName().getOrDefault(calleeIdentity.memberNameId(), List.of());
final var intrinsicCandidates = resolveIntrinsicCandidates(callExpr, calleeIdentity, context);
var categoryCount = 0;
if (callableCandidates != null && !callableCandidates.isEmpty()) {
@ -843,6 +931,66 @@ public final class PbsFrontendCompiler {
callExpr.span()));
}
private List<IRReservedMetadata.IntrinsicSurface> resolveIntrinsicCandidates(
final PbsAst.CallExpr callExpr,
final CalleeIdentity calleeIdentity,
final ExecutableLoweringContext context) {
final var receiverOwner = resolveReceiverOwnerForCallee(callExpr.callee(), context);
if (receiverOwner.isBlank()) {
return List.of();
}
final var key = new IntrinsicOwnerMethodKey(receiverOwner, calleeIdentity.memberSourceMethodName());
return context.intrinsicByOwnerAndMethod().getOrDefault(key, List.of());
}
private String resolveReceiverOwnerForCallee(
final PbsAst.Expression callee,
final ExecutableLoweringContext context) {
return switch (callee) {
case PbsAst.MemberExpr memberExpr -> resolveExpressionOwner(memberExpr.receiver(), context);
case PbsAst.GroupExpr groupExpr -> resolveReceiverOwnerForCallee(groupExpr.expression(), context);
default -> "";
};
}
private String resolveExpressionOwner(
final PbsAst.Expression expression,
final ExecutableLoweringContext context) {
if (expression == null) {
return "";
}
return switch (expression) {
case PbsAst.IdentifierExpr identifierExpr ->
context.builtinConstOwnerByNameId().getOrDefault(context.nameTable().register(identifierExpr.name()), "");
case PbsAst.CallExpr callExpr -> resolveCallReturnOwner(callExpr, context);
case PbsAst.MemberExpr memberExpr -> resolveExpressionOwner(memberExpr.receiver(), context);
case PbsAst.GroupExpr groupExpr -> resolveExpressionOwner(groupExpr.expression(), context);
default -> "";
};
}
private String resolveCallReturnOwner(
final PbsAst.CallExpr callExpr,
final ExecutableLoweringContext context) {
final var calleeIdentity = resolveCalleeIdentity(callExpr.callee(), context.nameTable());
if (calleeIdentity == null) {
return "";
}
final var receiverOwner = resolveReceiverOwnerForCallee(callExpr.callee(), context);
if (receiverOwner.isBlank()) {
return "";
}
final var key = new IntrinsicOwnerMethodKey(receiverOwner, calleeIdentity.memberSourceMethodName());
final var intrinsicCandidates = context.intrinsicByOwnerAndMethod().getOrDefault(key, List.of());
if (intrinsicCandidates.size() != 1) {
return "";
}
final var intrinsic = intrinsicCandidates.getFirst();
return context.intrinsicReturnOwnerByCanonical().getOrDefault(
new IntrinsicCanonicalKey(intrinsic.canonicalName(), intrinsic.canonicalVersion()),
"");
}
private CalleeIdentity resolveCalleeIdentity(
final PbsAst.Expression callee,
final NameTable nameTable) {
@ -853,6 +1001,7 @@ public final class PbsFrontendCompiler {
identifierExpr.name(),
null,
"",
identifierExpr.name(),
nameTable.register(identifierExpr.name()));
case PbsAst.MemberExpr memberExpr -> {
final var memberName = memberExpr.memberName();
@ -874,6 +1023,7 @@ public final class PbsFrontendCompiler {
qualified,
secondaryNameId,
secondaryDisplayName,
memberName,
nameTable.register(memberName));
}
case PbsAst.BindExpr bindExpr ->
@ -882,6 +1032,7 @@ public final class PbsFrontendCompiler {
bindExpr.functionName(),
null,
"",
bindExpr.functionName(),
nameTable.register(bindExpr.functionName()));
case PbsAst.GroupExpr groupExpr -> resolveCalleeIdentity(groupExpr.expression(), nameTable);
default -> null;
@ -1145,7 +1296,9 @@ public final class PbsFrontendCompiler {
private final DiagnosticSink diagnostics;
private final NameTable nameTable;
private final Map<NameId, List<IRReservedMetadata.HostMethodBinding>> hostByMethodName;
private final Map<NameId, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByMethodName;
private final Map<IntrinsicOwnerMethodKey, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByOwnerAndMethod;
private final Map<IntrinsicCanonicalKey, String> intrinsicReturnOwnerByCanonical;
private final Map<NameId, String> builtinConstOwnerByNameId;
private final Map<CallableResolutionKey, List<CallableId>> callableIdsByNameAndArity;
private final Map<CallableId, Integer> returnSlotsByCallableId;
private final IntrinsicTable intrinsicIdTable;
@ -1159,7 +1312,9 @@ public final class PbsFrontendCompiler {
final DiagnosticSink diagnostics,
final NameTable nameTable,
final Map<NameId, List<IRReservedMetadata.HostMethodBinding>> hostByMethodName,
final Map<NameId, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByMethodName,
final Map<IntrinsicOwnerMethodKey, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByOwnerAndMethod,
final Map<IntrinsicCanonicalKey, String> intrinsicReturnOwnerByCanonical,
final Map<NameId, String> builtinConstOwnerByNameId,
final Map<CallableResolutionKey, List<CallableId>> callableIdsByNameAndArity,
final Map<CallableId, Integer> returnSlotsByCallableId,
final IntrinsicTable intrinsicIdTable,
@ -1168,7 +1323,9 @@ public final class PbsFrontendCompiler {
this.diagnostics = diagnostics;
this.nameTable = nameTable;
this.hostByMethodName = hostByMethodName;
this.intrinsicByMethodName = intrinsicByMethodName;
this.intrinsicByOwnerAndMethod = intrinsicByOwnerAndMethod;
this.intrinsicReturnOwnerByCanonical = intrinsicReturnOwnerByCanonical;
this.builtinConstOwnerByNameId = builtinConstOwnerByNameId;
this.callableIdsByNameAndArity = callableIdsByNameAndArity;
this.returnSlotsByCallableId = returnSlotsByCallableId;
this.intrinsicIdTable = intrinsicIdTable;
@ -1191,8 +1348,16 @@ public final class PbsFrontendCompiler {
return hostByMethodName;
}
private Map<NameId, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByMethodName() {
return intrinsicByMethodName;
private Map<IntrinsicOwnerMethodKey, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByOwnerAndMethod() {
return intrinsicByOwnerAndMethod;
}
private Map<IntrinsicCanonicalKey, String> intrinsicReturnOwnerByCanonical() {
return intrinsicReturnOwnerByCanonical;
}
private Map<NameId, String> builtinConstOwnerByNameId() {
return builtinConstOwnerByNameId;
}
private Map<CallableResolutionKey, List<CallableId>> callableIdsByNameAndArity() {
@ -1255,11 +1420,22 @@ public final class PbsFrontendCompiler {
int arity) {
}
private record IntrinsicOwnerMethodKey(
String ownerCanonicalName,
String sourceMethodName) {
}
private record IntrinsicCanonicalKey(
String canonicalName,
long canonicalVersion) {
}
private record CalleeIdentity(
NameId primaryCallableNameId,
String primaryCallableDisplayName,
NameId secondaryCallableNameId,
String secondaryCallableDisplayName,
String memberSourceMethodName,
NameId memberNameId) {
}

View File

@ -867,6 +867,70 @@ class PBSFrontendPhaseServiceTest {
+ executableNames);
}
@Test
void shouldResolveIntrinsicChainsWithMethodNameCollisionsByOwnerIdentity() throws IOException {
final var projectRoot = tempDir.resolve("project-intrinsic-owner-collision");
final var sourceRoot = projectRoot.resolve("src");
Files.createDirectories(sourceRoot);
final var sourceFile = sourceRoot.resolve("main.pbs");
final var modBarrel = sourceRoot.resolve("mod.barrel");
Files.writeString(sourceFile, """
import { Input } from @sdk:input;
fn frame() -> void
{
Input.pad().x().pressed();
Input.touch().x();
}
""");
Files.writeString(modBarrel, "pub fn frame() -> void;");
final var projectTable = new ProjectTable();
final var fileTable = new FileTable(1);
final var projectId = projectTable.register(ProjectDescriptor.builder()
.rootPath(projectRoot)
.name("main")
.version("1.0.0")
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
.build());
registerFile(projectId, projectRoot, sourceFile, fileTable);
registerFile(projectId, projectRoot, modBarrel, fileTable);
final var ctx = new FrontendPhaseContext(
projectTable,
fileTable,
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
1);
final var diagnostics = DiagnosticSink.empty();
final var irBackend = new PBSFrontendPhaseService().compile(
ctx,
diagnostics,
LogAggregator.empty(),
BuildingIssueSink.empty());
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name())),
diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
final var frameExecutable = irBackend.getExecutableFunctions().stream()
.filter(function -> "frame".equals(function.callableName()))
.findFirst()
.orElseThrow();
final var intrinsicCalls = frameExecutable.instructions().stream()
.filter(instruction ->
instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC
&& instruction.intrinsicCall() != null)
.map(instruction -> instruction.intrinsicCall().canonicalName())
.toList();
assertTrue(intrinsicCalls.contains("input.pad"));
assertTrue(intrinsicCalls.contains("input.pad.x"));
assertTrue(intrinsicCalls.contains("input.button.pressed"));
assertTrue(intrinsicCalls.contains("input.touch"));
assertTrue(intrinsicCalls.contains("input.touch.x"));
}
private void registerFile(
final ProjectId projectId,
final Path projectRoot,