pr 06
This commit is contained in:
parent
8c02f505d0
commit
836b280998
@ -1,4 +1,4 @@
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticLevel};
|
||||
use crate::common::diagnostics::{Diagnostic, Severity};
|
||||
use crate::common::spans::Span;
|
||||
use crate::frontends::pbs::ast::{AstArena, NodeId};
|
||||
use prometeu_analysis::NameId;
|
||||
@ -89,10 +89,12 @@ impl DefIndex {
|
||||
pub fn insert_symbol(&mut self, key: DefKey, symbol_id: SymbolId) -> Result<(), Diagnostic> {
|
||||
if self.symbols.contains_key(&key) {
|
||||
return Err(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_DUPLICATE_SYMBOL".to_string(),
|
||||
message: "Duplicate symbol in the same module and namespace".to_string(),
|
||||
span: None,
|
||||
// Placeholder span; callers should overwrite with accurate span when known.
|
||||
span: Span::new(0, 0, 0),
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -215,7 +217,7 @@ mod tests {
|
||||
assert!(index.insert_symbol(key, SymbolId(0)).is_ok());
|
||||
let err = index.insert_symbol(key, SymbolId(1)).unwrap_err();
|
||||
|
||||
assert_eq!(err.code, Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()));
|
||||
assert_eq!(err.code, "E_RESOLVE_DUPLICATE_SYMBOL");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -149,12 +149,13 @@ pub fn compile_project(
|
||||
for sym in ts.symbols.into_values() {
|
||||
if let Some(existing) = ms.type_symbols.get(sym.name) {
|
||||
return Err(DiagnosticBundle::error(
|
||||
"E_RESOLVE_DUPLICATE_SYMBOL",
|
||||
format!(
|
||||
"Duplicate type symbol '{}' in module '{}'",
|
||||
interner.resolve(existing.name),
|
||||
module_path
|
||||
),
|
||||
Some(existing.span)
|
||||
existing.span,
|
||||
).into());
|
||||
}
|
||||
let _ = ms.type_symbols.insert(sym);
|
||||
@ -162,12 +163,13 @@ pub fn compile_project(
|
||||
for sym in vs.symbols.into_values() {
|
||||
if let Some(existing) = ms.value_symbols.get(sym.name) {
|
||||
return Err(DiagnosticBundle::error(
|
||||
"E_RESOLVE_DUPLICATE_SYMBOL",
|
||||
format!(
|
||||
"Duplicate value symbol '{}' in module '{}'",
|
||||
interner.resolve(existing.name),
|
||||
module_path
|
||||
),
|
||||
Some(existing.span)
|
||||
existing.span,
|
||||
).into());
|
||||
}
|
||||
let _ = ms.value_symbols.insert(sym);
|
||||
|
||||
@ -3,30 +3,30 @@ use crate::common::spans::Span;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DiagnosticLevel {
|
||||
pub enum Severity {
|
||||
Error,
|
||||
Warning,
|
||||
}
|
||||
|
||||
impl Serialize for DiagnosticLevel {
|
||||
impl Serialize for Severity {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
DiagnosticLevel::Error => serializer.serialize_str("error"),
|
||||
DiagnosticLevel::Warning => serializer.serialize_str("warning"),
|
||||
Severity::Error => serializer.serialize_str("error"),
|
||||
Severity::Warning => serializer.serialize_str("warning"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Diagnostic {
|
||||
#[serde(rename = "severity")]
|
||||
pub level: DiagnosticLevel,
|
||||
pub code: Option<String>,
|
||||
pub severity: Severity,
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
pub span: Option<Span>,
|
||||
pub span: Span,
|
||||
pub related: Vec<(String, Span)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
@ -45,13 +45,14 @@ impl DiagnosticBundle {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
pub fn error(message: String, span: Option<Span>) -> Self {
|
||||
pub fn error(code: &str, message: String, span: Span) -> Self {
|
||||
let mut bundle = Self::new();
|
||||
bundle.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: None,
|
||||
severity: Severity::Error,
|
||||
code: code.to_string(),
|
||||
message,
|
||||
span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
bundle
|
||||
}
|
||||
@ -59,10 +60,11 @@ impl DiagnosticBundle {
|
||||
pub fn has_errors(&self) -> bool {
|
||||
self.diagnostics
|
||||
.iter()
|
||||
.any(|d| matches!(d.level, DiagnosticLevel::Error))
|
||||
.any(|d| matches!(d.severity, Severity::Error))
|
||||
}
|
||||
|
||||
/// Serializes the diagnostic bundle to canonical JSON, resolving file IDs via FileManager.
|
||||
/// The output is deterministic: diagnostics are sorted by (file_id, start, end, code).
|
||||
pub fn to_json(&self, file_manager: &FileManager) -> String {
|
||||
#[derive(Serialize)]
|
||||
struct CanonicalSpan {
|
||||
@ -73,30 +75,67 @@ impl DiagnosticBundle {
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CanonicalDiag {
|
||||
severity: DiagnosticLevel,
|
||||
severity: Severity,
|
||||
code: String,
|
||||
message: String,
|
||||
span: Option<CanonicalSpan>,
|
||||
span: CanonicalSpan,
|
||||
related: Vec<(String, CanonicalSpan)>,
|
||||
}
|
||||
|
||||
let canonical_diags: Vec<CanonicalDiag> = self.diagnostics.iter().map(|d| {
|
||||
let canonical_span = d.span.and_then(|s| {
|
||||
file_manager.get_path(s.file_id).map(|p| {
|
||||
CanonicalSpan {
|
||||
file: p.file_name().unwrap().to_string_lossy().to_string(),
|
||||
start: s.start,
|
||||
end: s.end,
|
||||
}
|
||||
})
|
||||
});
|
||||
let mut diags = self.diagnostics.clone();
|
||||
diags.sort_by(|a, b| {
|
||||
(a.span.file_id, a.span.start, a.span.end, &a.code)
|
||||
.cmp(&(b.span.file_id, b.span.start, b.span.end, &b.code))
|
||||
});
|
||||
|
||||
CanonicalDiag {
|
||||
severity: d.level.clone(),
|
||||
code: d.code.clone().unwrap_or_else(|| "E_UNKNOWN".to_string()),
|
||||
message: d.message.clone(),
|
||||
span: canonical_span,
|
||||
}
|
||||
}).collect();
|
||||
let canonical_diags: Vec<CanonicalDiag> = diags
|
||||
.iter()
|
||||
.map(|d| {
|
||||
let s = d.span;
|
||||
let file = if s.file_id == usize::MAX {
|
||||
"<virtual>".to_string()
|
||||
} else {
|
||||
file_manager
|
||||
.get_path(s.file_id)
|
||||
.and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string()))
|
||||
.unwrap_or_else(|| format!("file_{}", s.file_id))
|
||||
};
|
||||
let canonical_span = CanonicalSpan {
|
||||
file,
|
||||
start: s.start,
|
||||
end: s.end,
|
||||
};
|
||||
|
||||
let related = d
|
||||
.related
|
||||
.iter()
|
||||
.map(|(msg, sp)| {
|
||||
let file = if sp.file_id == usize::MAX {
|
||||
"<virtual>".to_string()
|
||||
} else {
|
||||
file_manager
|
||||
.get_path(sp.file_id)
|
||||
.and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string()))
|
||||
.unwrap_or_else(|| format!("file_{}", sp.file_id))
|
||||
};
|
||||
let rsp = CanonicalSpan {
|
||||
file,
|
||||
start: sp.start,
|
||||
end: sp.end,
|
||||
};
|
||||
(msg.clone(), rsp)
|
||||
})
|
||||
.collect();
|
||||
|
||||
CanonicalDiag {
|
||||
severity: d.severity.clone(),
|
||||
code: d.code.clone(),
|
||||
message: d.message.clone(),
|
||||
span: canonical_span,
|
||||
related,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
serde_json::to_string_pretty(&canonical_diags).unwrap()
|
||||
}
|
||||
@ -109,66 +148,3 @@ impl From<Diagnostic> for DiagnosticBundle {
|
||||
bundle
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::common::files::FileManager;
|
||||
use crate::frontends::pbs::PbsFrontend;
|
||||
use crate::frontends::Frontend;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn get_diagnostics(code: &str) -> String {
|
||||
let mut file_manager = FileManager::new();
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let file_path = temp_dir.path().join("main.pbs");
|
||||
fs::write(&file_path, code).unwrap();
|
||||
|
||||
let frontend = PbsFrontend;
|
||||
match frontend.compile_to_ir(&file_path, &mut file_manager) {
|
||||
Ok(_) => "[]".to_string(),
|
||||
Err(bundle) => bundle.to_json(&file_manager),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_golden_parse_error() {
|
||||
let code = "fn main() { let x = ; }";
|
||||
let json = get_diagnostics(code);
|
||||
assert!(json.contains("E_PARSE_UNEXPECTED_TOKEN"));
|
||||
assert!(json.contains("Expected expression"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_golden_lex_error() {
|
||||
let code = "fn main() { let x = \"hello ; }";
|
||||
let json = get_diagnostics(code);
|
||||
assert!(json.contains("E_LEX_UNTERMINATED_STRING"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_golden_resolve_error() {
|
||||
let code = "fn main() { let x = undefined_var; }";
|
||||
let json = get_diagnostics(code);
|
||||
assert!(json.contains("E_RESOLVE_UNDEFINED"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_golden_type_error() {
|
||||
let code = "fn main() { let x: int = \"hello\"; }";
|
||||
let json = get_diagnostics(code);
|
||||
assert!(json.contains("E_TYPE_MISMATCH"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_golden_namespace_collision() {
|
||||
let code = "
|
||||
declare struct Foo {}
|
||||
fn main() {
|
||||
let Foo = 1;
|
||||
}
|
||||
";
|
||||
let json = get_diagnostics(code);
|
||||
assert!(json.contains("E_RESOLVE_NAMESPACE_COLLISION"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity};
|
||||
use crate::frontends::pbs::ast::*;
|
||||
use crate::frontends::pbs::symbols::*;
|
||||
use crate::semantics::export_surface::ExportSurfaceKind;
|
||||
@ -30,8 +30,9 @@ impl<'a> SymbolCollector<'a> {
|
||||
NodeKind::File(file) => file,
|
||||
_ => {
|
||||
return Err(DiagnosticBundle::error(
|
||||
"E_COLLECT_INVALID_ROOT",
|
||||
"Expected File node as root".to_string(),
|
||||
None,
|
||||
arena.span(root),
|
||||
))
|
||||
}
|
||||
};
|
||||
@ -165,38 +166,41 @@ impl<'a> SymbolCollector<'a> {
|
||||
|
||||
fn error_duplicate(&mut self, symbol: &Symbol, existing: &Symbol) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_DUPLICATE_SYMBOL".to_string(),
|
||||
message: format!(
|
||||
"Duplicate symbol '{}' already defined at {:?}",
|
||||
self.interner.resolve(symbol.name),
|
||||
existing.span
|
||||
),
|
||||
span: Some(symbol.span),
|
||||
span: symbol.span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
fn error_collision(&mut self, symbol: &Symbol, existing: &Symbol) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_NAMESPACE_COLLISION".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_NAMESPACE_COLLISION".to_string(),
|
||||
message: format!(
|
||||
"DebugSymbol '{}' collides with another symbol in the {:?} namespace defined at {:?}",
|
||||
self.interner.resolve(symbol.name),
|
||||
existing.namespace,
|
||||
existing.span
|
||||
),
|
||||
span: Some(symbol.span),
|
||||
span: symbol.span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
fn check_export_eligibility(&mut self, kind: SymbolKind, vis: Visibility, span: crate::common::spans::Span) {
|
||||
if let Err(msg) = ExportSurfaceKind::validate_visibility(kind, vis) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_SEMANTIC_EXPORT_RESTRICTION".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_SEMANTIC_EXPORT_RESTRICTION".to_string(),
|
||||
message: msg,
|
||||
span: Some(span),
|
||||
span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity};
|
||||
use crate::common::spans::Span;
|
||||
use crate::frontends::pbs::ast::*;
|
||||
use crate::frontends::pbs::contracts::ContractRegistry;
|
||||
@ -88,10 +88,11 @@ impl<'a> Lowerer<'a> {
|
||||
|
||||
fn error(&mut self, code: &str, message: String, span: crate::common::spans::Span) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some(code.to_string()),
|
||||
severity: Severity::Error,
|
||||
code: code.to_string(),
|
||||
message,
|
||||
span: Some(span),
|
||||
span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -100,8 +101,9 @@ impl<'a> Lowerer<'a> {
|
||||
NodeKind::File(file) => file,
|
||||
_ => {
|
||||
return Err(DiagnosticBundle::error(
|
||||
"E_LOWER_INVALID_ROOT",
|
||||
"Expected File node as root".to_string(),
|
||||
None,
|
||||
self.arena.span(root),
|
||||
))
|
||||
}
|
||||
};
|
||||
@ -1854,7 +1856,7 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
let bundle = result.err().unwrap();
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_LOWER_UNSUPPORTED".to_string())));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_LOWER_UNSUPPORTED"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1882,7 +1884,7 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
let bundle = result.err().unwrap();
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_LOWER_UNSUPPORTED".to_string())));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_LOWER_UNSUPPORTED"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1909,7 +1911,7 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
let bundle = result.err().unwrap();
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string())));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -2044,7 +2046,7 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
let bundle = result.err().unwrap();
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string())));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED"));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined function 'missing_func'")));
|
||||
}
|
||||
|
||||
@ -2071,7 +2073,7 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
let bundle = result.err().unwrap();
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string())));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED"));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined identifier 'undefined_var'")));
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,11 @@ impl Frontend for PbsFrontend {
|
||||
file_manager: &mut FileManager,
|
||||
) -> Result<ir_vm::Module, DiagnosticBundle> {
|
||||
let source = std::fs::read_to_string(entry).map_err(|e| {
|
||||
DiagnosticBundle::error(format!("Failed to read file: {}", e), None)
|
||||
crate::common::diagnostics::DiagnosticBundle::error(
|
||||
"E_FRONTEND_IO",
|
||||
format!("Failed to read file: {}", e),
|
||||
crate::common::spans::Span::new(usize::MAX, 0, 0),
|
||||
)
|
||||
})?;
|
||||
let file_id = file_manager.add(entry.to_path_buf(), source.clone());
|
||||
|
||||
@ -81,12 +85,20 @@ impl Frontend for PbsFrontend {
|
||||
|
||||
// Validate Core IR Invariants
|
||||
crate::ir_core::validate_program(&core_program).map_err(|e| {
|
||||
DiagnosticBundle::error(format!("Core IR Invariant Violation: {}", e), None)
|
||||
crate::common::diagnostics::DiagnosticBundle::error(
|
||||
"E_CORE_INVARIANT",
|
||||
format!("Core IR Invariant Violation: {}", e),
|
||||
crate::common::spans::Span::new(usize::MAX, 0, 0),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Lower Core IR to VM IR
|
||||
core_to_vm::lower_program(&core_program).map_err(|e| {
|
||||
DiagnosticBundle::error(format!("Lowering error: {}", e), None)
|
||||
crate::common::diagnostics::DiagnosticBundle::error(
|
||||
"E_LOWERING",
|
||||
format!("Lowering error: {}", e),
|
||||
crate::common::spans::Span::new(usize::MAX, 0, 0),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity};
|
||||
use crate::common::spans::Span;
|
||||
use crate::frontends::pbs::ast::*;
|
||||
use crate::frontends::pbs::lexer::Lexer;
|
||||
@ -1040,10 +1040,11 @@ impl<'a> Parser<'a> {
|
||||
|
||||
fn error_with_code(&mut self, message: &str, code: Option<&str>) -> DiagnosticBundle {
|
||||
let diag = Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: code.map(|c| c.to_string()),
|
||||
severity: Severity::Error,
|
||||
code: code.unwrap_or("E_PARSE_ERROR").to_string(),
|
||||
message: message.to_string(),
|
||||
span: Some(self.peek().span),
|
||||
span: self.peek().span,
|
||||
related: Vec::new(),
|
||||
};
|
||||
self.errors.push(diag.clone());
|
||||
DiagnosticBundle::from(diag)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticLevel};
|
||||
use crate::common::diagnostics::{Diagnostic, Severity};
|
||||
use crate::frontends::pbs::ast::{AstArena, NodeKind, NodeId};
|
||||
use crate::analysis::symbols::{Symbol, SymbolArena, SymbolKind, DefIndex, DefKey, Namespace, NodeToSymbol, RefIndex};
|
||||
use prometeu_analysis::NameInterner;
|
||||
@ -60,7 +60,7 @@ pub fn build_def_index(
|
||||
};
|
||||
|
||||
if let Err(mut diag) = index.insert_symbol(key, symbol_id) {
|
||||
diag.span = Some(span);
|
||||
diag.span = span;
|
||||
diagnostics.push(diag);
|
||||
}
|
||||
}
|
||||
@ -132,28 +132,31 @@ fn walk_node(
|
||||
let symbol = import_arena.get(symbol_id);
|
||||
if !symbol.exported && symbol.module != module {
|
||||
diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_VISIBILITY".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_VISIBILITY".to_string(),
|
||||
message: format!("Symbol is not exported from module {}", symbol.module),
|
||||
span: Some(span),
|
||||
span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
ref_index.record_ref(symbol_id, span);
|
||||
node_to_symbol.bind_node(node_id, symbol_id);
|
||||
} else {
|
||||
diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_UNDEFINED_IDENTIFIER".to_string()),
|
||||
message: format!("Undefined identifier"),
|
||||
span: Some(span),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_UNDEFINED_IDENTIFIER".to_string(),
|
||||
message: "Undefined identifier".to_string(),
|
||||
span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_UNDEFINED_IDENTIFIER".to_string()),
|
||||
message: format!("Undefined identifier"),
|
||||
span: Some(span),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_UNDEFINED_IDENTIFIER".to_string(),
|
||||
message: "Undefined identifier".to_string(),
|
||||
span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -330,8 +333,8 @@ mod tests {
|
||||
let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner, None);
|
||||
|
||||
assert_eq!(diagnostics.len(), 1);
|
||||
assert_eq!(diagnostics[0].code, Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()));
|
||||
assert_eq!(diagnostics[0].span, Some(Span::new(0, 30, 40)));
|
||||
assert_eq!(diagnostics[0].code, "E_RESOLVE_DUPLICATE_SYMBOL");
|
||||
assert_eq!(diagnostics[0].span, Span::new(0, 30, 40));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -393,8 +396,8 @@ mod tests {
|
||||
let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner, None);
|
||||
|
||||
assert_eq!(diagnostics.len(), 1);
|
||||
assert_eq!(diagnostics[0].code, Some("E_RESOLVE_UNDEFINED_IDENTIFIER".to_string()));
|
||||
assert_eq!(diagnostics[0].span, Some(Span::new(0, 50, 60)));
|
||||
assert_eq!(diagnostics[0].code, "E_RESOLVE_UNDEFINED_IDENTIFIER");
|
||||
assert_eq!(diagnostics[0].span, Span::new(0, 50, 60));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -496,8 +499,8 @@ mod tests {
|
||||
build_def_index(&arena2, 2, &interner, Some((&symbols1, &index1)));
|
||||
|
||||
assert_eq!(diagnostics.len(), 1);
|
||||
assert_eq!(diagnostics[0].code, Some("E_RESOLVE_VISIBILITY".to_string()));
|
||||
assert_eq!(diagnostics[0].span, Some(Span::new(0, 50, 62)));
|
||||
assert_eq!(diagnostics[0].code, "E_RESOLVE_VISIBILITY");
|
||||
assert_eq!(diagnostics[0].span, Span::new(0, 50, 62));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::analysis::symbols::{SymbolArena, SymbolId, NodeToSymbol};
|
||||
use crate::analysis::types::{TypeArena, TypeFacts, TypeId, TypeKind};
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity};
|
||||
use crate::common::spans::Span;
|
||||
use crate::frontends::pbs::ast::*;
|
||||
use crate::frontends::pbs::symbols::*;
|
||||
@ -60,8 +60,9 @@ impl<'a> Resolver<'a> {
|
||||
NodeKind::File(file) => file,
|
||||
_ => {
|
||||
return Err(DiagnosticBundle::error(
|
||||
"E_RESOLVE_INVALID_ROOT",
|
||||
"Expected File node as root".to_string(),
|
||||
None,
|
||||
arena.span(root),
|
||||
))
|
||||
}
|
||||
};
|
||||
@ -162,10 +163,11 @@ impl<'a> Resolver<'a> {
|
||||
NodeKind::ImportSpec(spec) => spec,
|
||||
_ => {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_INVALID_IMPORT".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_INVALID_IMPORT".to_string(),
|
||||
message: "Invalid import spec".to_string(),
|
||||
span: Some(arena.span(imp_id)),
|
||||
span: arena.span(imp_id),
|
||||
related: Vec::new(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -201,10 +203,11 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
} else {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_INVALID_IMPORT".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_INVALID_IMPORT".to_string(),
|
||||
message: format!("Module not found: {}", imp.from),
|
||||
span: Some(arena.span(imp_id)),
|
||||
span: arena.span(imp_id),
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -255,10 +258,11 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
} else {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_MISMATCH".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_MISMATCH".to_string(),
|
||||
message: "Unary '-' operator expects 'int'".to_string(),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -272,10 +276,11 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
} else {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_MISMATCH".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_MISMATCH".to_string(),
|
||||
message: "Unary '!' operator expects 'bool'".to_string(),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -301,10 +306,11 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
} else {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_MISMATCH".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_MISMATCH".to_string(),
|
||||
message: format!("Binary '{}' operator expects 'int' operands", n.op),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -317,10 +323,11 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
} else {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_MISMATCH".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_MISMATCH".to_string(),
|
||||
message: format!("Binary '{}' operator expects operands of the same type", n.op),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -573,10 +580,11 @@ impl<'a> Resolver<'a> {
|
||||
// Actually, lookup_with_id checks imported_symbols too.
|
||||
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_UNKNOWN_TYPE".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_UNKNOWN_TYPE".to_string(),
|
||||
message: format!("Unknown type: {}", name),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
None
|
||||
}
|
||||
@ -586,10 +594,11 @@ impl<'a> Resolver<'a> {
|
||||
"optional" => {
|
||||
if n.args.len() != 1 {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_INVALID_ARGS".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_INVALID_ARGS".to_string(),
|
||||
message: "optional<T> expects exactly 1 argument".to_string(),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
return None;
|
||||
}
|
||||
@ -599,10 +608,11 @@ impl<'a> Resolver<'a> {
|
||||
"result" => {
|
||||
if n.args.len() != 2 {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_INVALID_ARGS".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_INVALID_ARGS".to_string(),
|
||||
message: "result<T, E> expects exactly 2 arguments".to_string(),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
return None;
|
||||
}
|
||||
@ -613,10 +623,11 @@ impl<'a> Resolver<'a> {
|
||||
"array" => {
|
||||
if n.args.len() < 1 || n.args.len() > 2 {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_INVALID_ARGS".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_INVALID_ARGS".to_string(),
|
||||
message: "array<T> or array<T>[N] expects 1 or 2 arguments".to_string(),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
return None;
|
||||
}
|
||||
@ -626,10 +637,11 @@ impl<'a> Resolver<'a> {
|
||||
NodeKind::IntLit(lit) => Some(lit.value as u32),
|
||||
_ => {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_INVALID_ARGS".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_INVALID_ARGS".to_string(),
|
||||
message: "Array length must be an integer literal".to_string(),
|
||||
span: Some(arena.span(n.args[1])),
|
||||
span: arena.span(n.args[1]),
|
||||
related: Vec::new(),
|
||||
});
|
||||
None
|
||||
}
|
||||
@ -641,10 +653,11 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
_ => {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_UNKNOWN_TYPE".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_UNKNOWN_TYPE".to_string(),
|
||||
message: format!("Unknown generic type: {}", base_name),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
None
|
||||
}
|
||||
@ -652,10 +665,11 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
_ => {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_NOT_A_TYPE".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_NOT_A_TYPE".to_string(),
|
||||
message: "Expected a type node".to_string(),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
None
|
||||
}
|
||||
@ -672,10 +686,11 @@ impl<'a> Resolver<'a> {
|
||||
} else {
|
||||
if namespace == Namespace::Type {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_UNKNOWN_TYPE".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_UNKNOWN_TYPE".to_string(),
|
||||
message: format!("Unknown type: {}", self.interner.resolve(name)),
|
||||
span: Some(span),
|
||||
span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
} else {
|
||||
self.error_undefined(name, span);
|
||||
@ -780,25 +795,27 @@ impl<'a> Resolver<'a> {
|
||||
// Check for collision in Type namespace at top-level?
|
||||
// Actually, the spec says "A name may not exist in both namespaces".
|
||||
// If we want to be strict, we check current module's type symbols too.
|
||||
if self.current_module.type_symbols.get(name).is_some() {
|
||||
if let Some(existing) = self.current_module.type_symbols.get(name) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_NAMESPACE_COLLISION".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_NAMESPACE_COLLISION".to_string(),
|
||||
message: format!(
|
||||
"Local variable '{}' collides with a type name",
|
||||
self.interner.resolve(name)
|
||||
),
|
||||
span: Some(span),
|
||||
span,
|
||||
related: vec![("type defined here".to_string(), existing.span)],
|
||||
});
|
||||
return None;
|
||||
}
|
||||
|
||||
if scope.contains_key(&name) {
|
||||
if let Some((prev_sym, _)) = scope.get(&name) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_DUPLICATE_SYMBOL".to_string(),
|
||||
message: format!("Duplicate local variable '{}'", self.interner.resolve(name)),
|
||||
span: Some(span),
|
||||
span,
|
||||
related: vec![("previous definition here".to_string(), prev_sym.span)],
|
||||
});
|
||||
None
|
||||
} else {
|
||||
@ -840,31 +857,34 @@ impl<'a> Resolver<'a> {
|
||||
|
||||
fn error_undefined(&mut self, name: NameId, span: Span) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_UNDEFINED".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_UNDEFINED".to_string(),
|
||||
message: format!("Undefined identifier: {}", self.interner.resolve(name)),
|
||||
span: Some(span),
|
||||
span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
fn error_duplicate_import(&mut self, name: NameId, span: Span) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_DUPLICATE_SYMBOL".to_string(),
|
||||
message: format!("Duplicate import: {}", self.interner.resolve(name)),
|
||||
span: Some(span),
|
||||
span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
fn error_visibility(&mut self, sym: &Symbol, span: Span) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_VISIBILITY".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_VISIBILITY".to_string(),
|
||||
message: format!(
|
||||
"DebugSymbol '{}' is not visible here",
|
||||
self.interner.resolve(sym.name)
|
||||
),
|
||||
span: Some(span),
|
||||
span,
|
||||
related: vec![("symbol defined here".to_string(), sym.span)],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -897,7 +917,7 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
let bundle = result.unwrap_err();
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string())));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_DUPLICATE_SYMBOL"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -912,7 +932,7 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
let bundle = result.unwrap_err();
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_NAMESPACE_COLLISION".to_string())));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_NAMESPACE_COLLISION"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -939,7 +959,7 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
let bundle = result.unwrap_err();
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string())));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1011,7 +1031,7 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
let bundle = result.unwrap_err();
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_VISIBILITY".to_string())));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_VISIBILITY"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1083,7 +1103,7 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
let bundle = result.unwrap_err();
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_INVALID_IMPORT".to_string())));
|
||||
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_INVALID_IMPORT"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity};
|
||||
use crate::common::spans::Span;
|
||||
use crate::frontends::pbs::ast::*;
|
||||
use crate::frontends::pbs::contracts::ContractRegistry;
|
||||
@ -51,8 +51,9 @@ impl<'a> TypeChecker<'a> {
|
||||
NodeKind::File(file) => file,
|
||||
_ => {
|
||||
return Err(DiagnosticBundle::error(
|
||||
"E_TYPECHECK_INVALID_ROOT",
|
||||
"Expected File node as root".to_string(),
|
||||
None,
|
||||
arena.span(root),
|
||||
))
|
||||
}
|
||||
};
|
||||
@ -261,10 +262,11 @@ impl<'a> TypeChecker<'a> {
|
||||
};
|
||||
} else {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_UNDEFINED".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_UNDEFINED".to_string(),
|
||||
message: format!("Method '{}' not found on host contract '{}'", member_str, name_str),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
return PbsType::Void;
|
||||
@ -372,10 +374,11 @@ impl<'a> TypeChecker<'a> {
|
||||
if obj_ty != PbsType::Void {
|
||||
let msg = format!("Member '{}' not found on type {:?}", member_str, obj_ty);
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_UNDEFINED".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_RESOLVE_UNDEFINED".to_string(),
|
||||
message: msg,
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
PbsType::Void
|
||||
@ -444,14 +447,15 @@ impl<'a> TypeChecker<'a> {
|
||||
// Void doesn't strictly need return
|
||||
} else {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_RETURN_PATH".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_RETURN_PATH".to_string(),
|
||||
message: format!(
|
||||
"Function '{}' must return a value of type {}",
|
||||
self.interner.resolve(n.name),
|
||||
return_type
|
||||
),
|
||||
span: Some(arena.span(id)),
|
||||
span: arena.span(id),
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -570,10 +574,11 @@ impl<'a> TypeChecker<'a> {
|
||||
PbsType::Function { params, return_type } => {
|
||||
if n.args.len() != params.len() {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_MISMATCH".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_MISMATCH".to_string(),
|
||||
message: format!("Expected {} arguments, found {}", params.len(), n.args.len()),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
} else {
|
||||
for (i, arg) in n.args.iter().enumerate() {
|
||||
@ -588,10 +593,11 @@ impl<'a> TypeChecker<'a> {
|
||||
_ => {
|
||||
if callee_ty != PbsType::Void {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_MISMATCH".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_MISMATCH".to_string(),
|
||||
message: format!("Type {} is not callable", callee_ty),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
PbsType::Void
|
||||
@ -789,10 +795,11 @@ impl<'a> TypeChecker<'a> {
|
||||
}
|
||||
} else {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_UNKNOWN_TYPE".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_UNKNOWN_TYPE".to_string(),
|
||||
message: format!("Unknown type: {}", name_str),
|
||||
span: Some(arena.span(node)),
|
||||
span: arena.span(node),
|
||||
related: Vec::new(),
|
||||
});
|
||||
PbsType::Void
|
||||
}
|
||||
@ -912,10 +919,11 @@ impl<'a> TypeChecker<'a> {
|
||||
|
||||
fn error_type_mismatch(&mut self, expected: &PbsType, found: &PbsType, span: Span) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_TYPE_MISMATCH".to_string()),
|
||||
severity: Severity::Error,
|
||||
code: "E_TYPE_MISMATCH".to_string(),
|
||||
message: format!("Type mismatch: expected {}, found {}", expected, found),
|
||||
span: Some(span),
|
||||
span,
|
||||
related: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -953,7 +961,7 @@ mod tests {
|
||||
Err(bundle) => {
|
||||
let mut errors = Vec::new();
|
||||
for diag in bundle.diagnostics {
|
||||
let code = diag.code.unwrap_or_else(|| "NO_CODE".to_string());
|
||||
let code = diag.code;
|
||||
errors.push(format!("{}: {}", code, diag.message));
|
||||
}
|
||||
let err_msg = errors.join(", ");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user