This commit is contained in:
bQUARKz 2026-02-10 20:03:03 +00:00
parent 2ba8507284
commit a4cc1487c4
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
8 changed files with 48 additions and 126 deletions

View File

@ -192,17 +192,32 @@ impl ExportRef {
} }
} }
/// Canonical function key, identifying an overload by arity only (for now). /// Canonical function identity.
/// This will be extended in PR-03.04 with types and calling convention. ///
/// JVM-like canonical pieces to uniquely identify an overload across projects:
/// - `owner`: optional service/type name for methods (e.g., `Some("Log")`) or `None` for free fns
/// - `name`: unqualified function/method name (e.g., `"debug"`)
/// - `sig`: canonical signature id produced by the frontend
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct CanonicalFnKey { pub struct CanonicalFnKey {
pub import: ImportRef, pub owner: Option<ItemName>,
pub arity: u16, pub name: ItemName,
pub sig: SignatureRef,
} }
impl CanonicalFnKey { impl CanonicalFnKey {
pub fn new(import: ImportRef, arity: u16) -> Self { Self { import, arity } } pub fn new(owner: Option<ItemName>, name: ItemName, sig: SignatureRef) -> Self {
Self { owner, name, sig }
}
/// Returns a human-friendly display name like "Log.debug" or just "debug".
pub fn debug_name(&self) -> String {
match &self.owner {
Some(o) => format!("{}.{}", o.as_str(), self.name.as_str()),
None => self.name.as_str().to_string(),
}
}
} }
/// Opaque canonical reference to a type known to the Frontend. /// Opaque canonical reference to a type known to the Frontend.

View File

@ -293,8 +293,13 @@ impl BytecodeEmitter {
stack_height = (stack_height - (*arg_count as i32)).max(0); stack_height = (stack_height - (*arg_count as i32)).max(0);
} }
} }
InstrKind::ImportCall { dep_alias, module_path, base_name, sig, arg_count } => { InstrKind::ImportCall { dep_alias, module_path, owner, base_name, sig, arg_count } => {
let label = format!("@{}::{}:{}#sig{}", dep_alias, module_path, base_name, sig.0); let display_name = if let Some(o) = owner {
format!("{}.{}", o, base_name)
} else {
base_name.clone()
};
let label = format!("@{}::{}:{}#sig{}", dep_alias, module_path, display_name, sig.0);
asm_instrs.push(Asm::Op(OpCode::Call, vec![Operand::Label(label)])); asm_instrs.push(Asm::Op(OpCode::Call, vec![Operand::Label(label)]));
stack_height = (stack_height - (*arg_count as i32)).max(0); stack_height = (stack_height - (*arg_count as i32)).max(0);
} }

View File

@ -1160,6 +1160,7 @@ impl<'a> Lowerer<'a> {
self.emit(InstrKind::ImportCall { self.emit(InstrKind::ImportCall {
dep_alias, dep_alias,
module_path, module_path,
owner: None,
base_name, base_name,
sig, sig,
arg_count: n.args.len() as u32, arg_count: n.args.len() as u32,
@ -1385,10 +1386,11 @@ impl<'a> Lowerer<'a> {
}; };
if let Some(sig) = sig_opt { if let Some(sig) = sig_opt {
let base_name = format!("{}.{}", obj_name, member_name); let base_name = member_name.to_string();
self.emit(InstrKind::ImportCall { self.emit(InstrKind::ImportCall {
dep_alias, dep_alias,
module_path, module_path,
owner: Some(obj_name.to_string()),
base_name, base_name,
sig, sig,
arg_count: n.args.len() as u32, arg_count: n.args.len() as u32,

View File

@ -24,10 +24,12 @@ pub enum InstrKind {
/// Placeholder for function calls. /// Placeholder for function calls.
Call(FunctionId, u32), Call(FunctionId, u32),
/// External calls (imports). /// External calls (imports).
/// Carries dependency alias, module path, base function name, precise signature id, and arg count. /// Carries dependency alias, module path, optional owner (service), base function name,
/// precise signature id, and arg count.
ImportCall { ImportCall {
dep_alias: String, dep_alias: String,
module_path: String, module_path: String,
owner: Option<String>,
base_name: String, base_name: String,
sig: SigId, sig: SigId,
arg_count: u32, arg_count: u32,

View File

@ -134,7 +134,11 @@ pub enum InstrKind {
ImportCall { ImportCall {
dep_alias: String, dep_alias: String,
module_path: String, module_path: String,
/// Optional service/type owner for methods (e.g., "Log"). `None` for free functions.
owner: Option<String>,
/// Unqualified function/method name (e.g., "debug").
base_name: String, base_name: String,
/// Exact signature id selected by the frontend.
sig: SigId, sig: SigId,
arg_count: u32, arg_count: u32,
}, },
@ -249,6 +253,7 @@ mod tests {
InstrKind::ImportCall { InstrKind::ImportCall {
dep_alias: "std".to_string(), dep_alias: "std".to_string(),
module_path: "math".to_string(), module_path: "math".to_string(),
owner: None,
base_name: "abs".to_string(), base_name: "abs".to_string(),
sig: SigId(1), sig: SigId(1),
arg_count: 1, arg_count: 1,
@ -343,6 +348,7 @@ mod tests {
"ImportCall": { "ImportCall": {
"dep_alias": "std", "dep_alias": "std",
"module_path": "math", "module_path": "math",
"owner": null,
"base_name": "abs", "base_name": "abs",
"sig": 1, "sig": 1,
"arg_count": 1 "arg_count": 1

View File

@ -124,7 +124,7 @@ pub fn lower_function(
arg_count: *arg_count arg_count: *arg_count
}, None)); }, None));
} }
ir_core::InstrKind::ImportCall { dep_alias, module_path, base_name, sig, arg_count } => { ir_core::InstrKind::ImportCall { dep_alias, module_path, owner, base_name, sig, arg_count } => {
// Pop arguments from type stack // Pop arguments from type stack
for _ in 0..*arg_count { for _ in 0..*arg_count {
stack_types.pop(); stack_types.pop();
@ -133,6 +133,7 @@ pub fn lower_function(
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::ImportCall { vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::ImportCall {
dep_alias: dep_alias.clone(), dep_alias: dep_alias.clone(),
module_path: module_path.clone(), module_path: module_path.clone(),
owner: owner.clone(),
base_name: base_name.clone(), base_name: base_name.clone(),
sig: *sig, sig: *sig,
arg_count: *arg_count, arg_count: *arg_count,

View File

@ -4,112 +4,13 @@
> >
> Strategy: **surgical PRs** that (1) stop PBS types from leaking, (2) replace stringy protocols with canonical models, and (3) make imports/exports/overloads deterministic across deps. > Strategy: **surgical PRs** that (1) stop PBS types from leaking, (2) replace stringy protocols with canonical models, and (3) make imports/exports/overloads deterministic across deps.
--- # Notes / Operating Rules (for Junie)
## PR-03.03 — Canonical import syntax → `ImportRef` (no dual styles) 1. **BE is the source of truth**: `frontend-api` defines canonical models; FE conforms.
2. **No string protocols** across layers. Strings may exist only as *display/debug*.
### Title 3. **No FE implementation imports from other FE implementations**.
4. **No BE imports PBS modules** (hard boundary).
Define single canonical import model and parse PBS imports into `ImportRef` 5. **Overload resolution is signature-based** (arity alone is not valid).
### Briefing / Context
We currently support multiple synthetic import path styles (`"alias/module"` and `"@alias:module"`). This amplifies ambiguity and is a root cause of mismatch in imported service method overloads.
We want **one** canonical representation:
* PBS syntax: `import { Test } from "@sdk:input/testing"`
* Canonical model: `ImportRef { project: "sdk", module: "input/testing", item: "Test" }`
### Target
* PBS FE produces a list of canonical `ImportRef`.
* BE consumes only `ImportRef`.
* Remove support for dual synthetic path style in the BE pipeline.
### Scope
* In PBS FE:
* Parse `@<alias>:<module_path>` into `ImportRef`.
* Validate module path normalization.
* Validate that `item` is a single symbol name (service/struct/host/contract/etc).
* In BE:
* Replace “synthetic path generation” with canonical module lookup using `(alias, module_path)`.
### Out of scope
* Export naming canonicalization (PR-03.04/03.05).
### Checklist
* [ ] Implement import parser → `ImportRef`.
* [ ] Remove `alias/module` synthetic path support.
* [ ] Update resolver/module-provider lookup to accept `(alias, module_path)`.
* [ ] Add diagnostics for invalid import string.
### Tests
* Unit tests in PBS FE for:
* valid: `"@sdk:input/testing"`
* invalid forms
* normalization edge cases (leading `/`, `./`, `\\` on Windows paths)
* Integration test (golden-style) compiling a small project importing a service.
### Risk
Medium. Changes import resolution plumbing.
---
## PR-03.04 — Canonical function identity: `CanonicalFnKey` (JVM-like)
### Title
Introduce canonical function identity for exports/import calls (no string prefix matching)
### Briefing / Context
Phase 03 currently tries to match overloads using `name#sigN` strings + prefix logic + origin checks. This breaks easily and is exactly what produced `E_OVERLOAD_NOT_FOUND` for `Log.debug`.
We need a **canonical function key** that is not “string protocol”:
* `CanonicalFnKey { owner: Option<ItemName>, name: ItemName, sig: SigId }`
* Free fn: `owner=None, name=foo, sig=...`
* Service method: `owner=Some(Log), name=debug, sig=...`
### Target
* BE uses `CanonicalFnKey` for export surface and import relocation.
* FE supplies `owner/name` and produces/requests signatures deterministically.
### Scope
* Add `CanonicalFnKey` to `frontend-api`.
* Update VM import call instruction payload to carry canonical pieces:
* `ImportCall { dep_alias, module_path, fn_key: CanonicalFnKey, arg_count }`
* (or equivalent)
* Eliminate string matching / prefix matching for overload selection.
### Checklist
* [ ] Define `CanonicalFnKey` and helpers.
* [ ] Update IR / bytecode instruction structures if needed.
* [ ] Update lowering call sites.
* [ ] Ensure debug info keeps readable names (owner.name).
### Tests
* Unit: canonical formatting for debug name `Log.debug`.
* Integration: two overloads of `Log.debug` across deps resolved by exact signature.
### Risk
High-ish. Touches instruction encoding and matching logic.
--- ---
@ -241,13 +142,3 @@ After canonical models are in place, we must delete compatibility code paths (`a
### Risk ### Risk
Low/Medium. Mostly deletion + docs, but could expose hidden dependencies. Low/Medium. Mostly deletion + docs, but could expose hidden dependencies.
---
# Notes / Operating Rules (for Junie)
1. **BE is the source of truth**: `frontend-api` defines canonical models; FE conforms.
2. **No string protocols** across layers. Strings may exist only as *display/debug*.
3. **No FE implementation imports from other FE implementations**.
4. **No BE imports PBS modules** (hard boundary).
5. **Overload resolution is signature-based** (arity alone is not valid).