pr 03.04
This commit is contained in:
parent
2ba8507284
commit
a4cc1487c4
@ -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.
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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).
|
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user