diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java index 517b8b18..c976b4bd 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java @@ -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 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>(); + final var builtinCanonicalBySourceType = new HashMap(); for (final var builtinType : reservedMetadata.builtinTypeSurfaces()) { + builtinCanonicalBySourceType.putIfAbsent(builtinType.sourceTypeName(), builtinType.canonicalTypeName()); + } + final var builtinConstOwnerByNameId = new HashMap(); + 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(); + final var topDeclsForIntrinsicInference = new ArrayList(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>(); + final var intrinsicReturnOwnerByCanonical = new HashMap(); + 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 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 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> hostByMethodName; - private final Map> intrinsicByMethodName; + private final Map> intrinsicByOwnerAndMethod; + private final Map intrinsicReturnOwnerByCanonical; + private final Map builtinConstOwnerByNameId; private final Map> callableIdsByNameAndArity; private final Map returnSlotsByCallableId; private final IntrinsicTable intrinsicIdTable; @@ -1159,7 +1312,9 @@ public final class PbsFrontendCompiler { final DiagnosticSink diagnostics, final NameTable nameTable, final Map> hostByMethodName, - final Map> intrinsicByMethodName, + final Map> intrinsicByOwnerAndMethod, + final Map intrinsicReturnOwnerByCanonical, + final Map builtinConstOwnerByNameId, final Map> callableIdsByNameAndArity, final Map 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> intrinsicByMethodName() { - return intrinsicByMethodName; + private Map> intrinsicByOwnerAndMethod() { + return intrinsicByOwnerAndMethod; + } + + private Map intrinsicReturnOwnerByCanonical() { + return intrinsicReturnOwnerByCanonical; + } + + private Map builtinConstOwnerByNameId() { + return builtinConstOwnerByNameId; } private Map> 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) { } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java index 1e4d24ec..d68c7de8 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java @@ -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,