pr 03.06
This commit is contained in:
parent
ab2ebba286
commit
a115281d88
@ -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,
|
||||
|
||||
@ -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),
|
||||
);
|
||||
|
||||
@ -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. That’s 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
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user