This commit is contained in:
bQUARKz 2026-02-10 21:01:43 +00:00
parent ab2ebba286
commit a115281d88
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
4 changed files with 83 additions and 72 deletions

View File

@ -1,4 +1,5 @@
use crate::frontends::pbs::{parser::Parser, SymbolCollector, ModuleSymbols, Resolver, ModuleProvider, Lowerer};
use crate::frontends::pbs::typecheck::TypeChecker;
use crate::lowering::core_to_vm;
use crate::common::spans::FileId;
use frontend_api::traits::{Frontend as CanonFrontend, FrontendUnit};
@ -62,9 +63,22 @@ impl CanonFrontend for PbsFrontendAdapter {
// Ensure primitives are interned in this FE-local interner
let primitives = ["int", "bool", "float", "string", "bounded", "void"];
for p in primitives { interner.intern(p); }
let mut resolver = Resolver::new(&module_symbols, &EmptyProvider, &interner);
resolver.bootstrap_types(&interner);
if let Err(d) = resolver.resolve(&parsed.arena, parsed.root) {
// Resolver scope (immutable borrow of module_symbols limited to this block)
let imported_symbols = {
let mut resolver = Resolver::new(&module_symbols, &EmptyProvider, &interner);
resolver.bootstrap_types(&interner);
if let Err(d) = resolver.resolve(&parsed.arena, parsed.root) {
diags.push(CanonDiagnostic { message: format!("{:?}", d), severity: CanonSeverity::Error });
return FrontendUnit { diagnostics: diags, imports: vec![], exports: vec![], lowered_ir: LoweredIr::default() };
}
resolver.imported_symbols.clone()
};
// Run PBS typechecker to compute function/method signatures and basic type info
// This ensures exported/imported symbols carry `PbsType::Function { params, .. }` so
// lowering can perform deterministic overload resolution by exact signature.
let mut tc = TypeChecker::new(&mut module_symbols, &imported_symbols, &EmptyProvider, &interner);
if let Err(d) = tc.check(&parsed.arena, parsed.root) {
diags.push(CanonDiagnostic { message: format!("{:?}", d), severity: CanonSeverity::Error });
return FrontendUnit { diagnostics: diags, imports: vec![], exports: vec![], lowered_ir: LoweredIr::default() };
}
@ -132,7 +146,7 @@ impl CanonFrontend for PbsFrontendAdapter {
}
// Lower to VM IR and wrap as LoweredIr bytes
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &resolver.imported_symbols, &EmptyProvider, &interner);
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported_symbols, &EmptyProvider, &interner);
let module_name = path.file_stem().unwrap().to_string_lossy();
let core_program = match lowerer.lower_file(parsed.root, &module_name) {
Ok(c) => c,

View File

@ -47,6 +47,42 @@ pub struct Lowerer<'a> {
}
impl<'a> Lowerer<'a> {
// Infer a minimal PbsType for an expression node. This is intentionally
// lightweight and only covers cases needed for overload resolution.
fn infer_pbs_type(&self, node: NodeId) -> PbsType {
match self.arena.kind(node) {
NodeKind::IntLit(_) => PbsType::Int,
NodeKind::FloatLit(_) => PbsType::Float,
NodeKind::BoundedLit(_) => PbsType::Bounded,
NodeKind::StringLit(_) => PbsType::String,
NodeKind::Ident(id) => {
// Try value symbols (current or imported)
if let Some(sym) = self.module_symbols.value_symbols.get(id.name)
.or_else(|| self.imported_symbols.value_symbols.get(id.name))
{
if let Some(ty) = &sym.ty {
return ty.clone();
}
}
// Try type symbols for constructors/constants resolved as identifiers
if let Some(sym) = self.module_symbols.type_symbols.get(id.name)
.or_else(|| self.imported_symbols.type_symbols.get(id.name))
{
match sym.kind {
SymbolKind::Struct => return PbsType::Struct(self.interner.resolve(id.name).to_string()),
SymbolKind::Service => return PbsType::Service(self.interner.resolve(id.name).to_string()),
SymbolKind::Contract => return PbsType::Contract(self.interner.resolve(id.name).to_string()),
SymbolKind::ErrorType => return PbsType::ErrorType(self.interner.resolve(id.name).to_string()),
_ => {}
}
}
PbsType::Void
}
// For member access and other expressions, we don't need deep inference for matching
// in v0 scope; return Void which will never match and will surface a deterministic error.
_ => PbsType::Void,
}
}
fn sig_from_pbs_fn(&self, pbs: &PbsType) -> Option<SigId> {
if let PbsType::Function { params, return_type } = pbs {
let mut core_params = Vec::with_capacity(params.len());
@ -1316,7 +1352,12 @@ impl<'a> Lowerer<'a> {
// Suporte a chamada estática de service: Service.method(...)
if sym.kind == SymbolKind::Service {
let full_name = format!("{}.{}", obj_name, member_name);
// Compute argument types first to resolve overload deterministically
let arg_types: Vec<PbsType> = n.args.iter().map(|a| self.infer_pbs_type(*a)).collect();
// Lower arguments after resolution logic decides the callee (stack order preserved)
for arg in &n.args { self.lower_node(*arg)?; }
if let Some(func_id) = self.function_ids.get(&full_name).cloned() {
self.emit(InstrKind::Call(func_id, n.args.len() as u32));
} else {
@ -1348,37 +1389,42 @@ impl<'a> Lowerer<'a> {
}
}
// If multiple candidates, try to disambiguate by arity (exact arg count)
let mut filtered: Vec<&Symbol> = candidates;
if filtered.len() > 1 {
let argc = n.args.len();
filtered = filtered.into_iter().filter(|s| {
if let Some(PbsType::Function { params, .. }) = &s.ty { params.len() == argc } else { false }
}).collect();
}
// Deterministic exact-match selection by full signature
let exact: Vec<&Symbol> = candidates.into_iter().filter(|s| {
if let Some(PbsType::Function { params, .. }) = &s.ty {
*params == arg_types
} else { false }
}).collect();
let sig_opt = if filtered.len() == 1 {
filtered[0]
.ty
.as_ref()
.and_then(|t| self.sig_from_pbs_fn(t))
} else if filtered.is_empty() {
let sig_opt = if exact.len() == 1 {
exact[0].ty.as_ref().and_then(|t| self.sig_from_pbs_fn(t))
} else if exact.is_empty() {
// Not found: stable diagnostic including provided arg types
let args_desc = {
let parts: Vec<String> = arg_types.iter().map(|t| t.to_string()).collect();
parts.join(", ")
};
self.error(
"E_OVERLOAD_NOT_FOUND",
format!(
"No matching overload for imported service method '{}.{}' with {} argument(s)",
obj_name, member_name, n.args.len()
"No matching overload for imported service method '{}.{}' with parameter types ({})",
obj_name, member_name, args_desc
),
self.arena.span(n.callee),
);
return Err(());
} else {
// Ambiguous within the bound module context; emit deterministic error
// Ambiguous: multiple exact matches; sort deterministically for diagnostics
let mut infos: Vec<String> = exact.iter().filter_map(|s| {
s.ty.as_ref().map(|t| t.to_string())
}).collect();
infos.sort();
let list = infos.join(", ");
self.error(
"E_OVERLOAD_AMBIGUOUS",
format!(
"Ambiguous imported service method '{}.{}' ({} candidates in module '{}')",
obj_name, member_name, filtered.len(), module_path
"Ambiguous imported service method '{}.{}' (candidates: [{}])",
obj_name, member_name, list
),
self.arena.span(n.callee),
);

View File

@ -14,55 +14,6 @@
---
## PR-03.06 — Deterministic overload resolution across deps (arity is not enough)
### Title
Implement deterministic overload selection using canonical signature matching
### Briefing / Context
We currently try to disambiguate overloads by arity as a fallback. Thats not sufficient (same arity, different types). For Phase 03 “professional grade”, overload resolution must be deterministic and match by full signature.
### Target
* Imported method call selects overload by:
1. resolve callee symbol → candidate set
2. typecheck args → determine expected param types
3. choose exact match
4. otherwise `E_OVERLOAD_NOT_FOUND` or `E_OVERLOAD_AMBIGUOUS` deterministically
### Scope
* PBS FE typechecker must provide enough info to compute signature selection.
* Resolver must expose all overload candidates for an imported `ImportRef` item.
* Lowering uses canonical fn key and selected `SigId`.
### Checklist
* [ ] Ensure imported service methods are actually present in imported symbol arena.
* [ ] Ensure candidates include `(owner, name, sig)` not just `name`.
* [ ] Implement exact-match algorithm.
* [ ] Implement deterministic ambiguity ordering for diagnostics.
### Tests
* Add golden regression reproducing `Log.debug` failure:
* dep exports `service Log { debug(string) }`
* root imports `Log` and calls `Log.debug("x")`
* Add tests for:
* ambiguous same signature
* not found
### Risk
Medium/High. Needs clean integration across resolver/typechecker/lowering.
---
## PR-03.07 — Phase 03 cleanup: remove legacy compatibility branches and document boundary
### Title