Skip to content

Commit 8b53bad

Browse files
committed
Reject duplicate top-level symbols
1 parent 6457f44 commit 8b53bad

5 files changed

Lines changed: 166 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ It currently implements:
2424
Implemented diagnostic classes include:
2525

2626
- file mode violations
27+
- duplicate top-level declarations that would make symbol resolution ambiguous
2728
- missing named arguments
2829
- unknown, missing, and duplicate call arguments for known signatures
2930
- unknown callees outside known functions, constructors, enum variants, and builtin signatures

src/analyzer.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::checks;
22
use crate::diagnostic::{Diagnostic, code};
3-
use crate::hir::{FunctionSig, Hir, HirTypeKind};
3+
use crate::hir::{DuplicateSymbolKind, FunctionSig, Hir, HirTypeKind};
44
use crate::lexer::{Token, lex};
55
use crate::syntax::ast::{Callee, EffectDecl, Item};
66
use crate::syntax::parse_source;
@@ -29,6 +29,7 @@ pub(crate) struct Analyzer<'a> {
2929
impl Analyzer<'_> {
3030
fn run(&mut self) {
3131
self.check_file_mode_present();
32+
self.check_duplicate_declarations();
3233
self.check_signature_explicitness();
3334
self.check_resource_fields();
3435
checks::mode::check(self);
@@ -116,6 +117,32 @@ impl Analyzer<'_> {
116117
}
117118
}
118119

120+
fn check_duplicate_declarations(&mut self) {
121+
for duplicate in self.hir.duplicate_symbols() {
122+
self.diagnostics.push(
123+
Diagnostic::error(
124+
code::DUPLICATE_DECLARATION,
125+
format!(
126+
"{} `{}` is declared more than once.",
127+
duplicate_symbol_label(duplicate.kind),
128+
duplicate.name
129+
),
130+
duplicate.duplicate_span.clone(),
131+
"duplicate declaration",
132+
)
133+
.with_cause(format!(
134+
"The first declaration is at {}:{}.",
135+
duplicate.first_span.line, duplicate.first_span.column
136+
))
137+
.with_fix(
138+
"rename_declaration",
139+
"Rename or remove one declaration so the symbol table is unambiguous.",
140+
"manual",
141+
),
142+
);
143+
}
144+
}
145+
119146
fn check_resource_fields(&mut self) {
120147
for item in &self.syntax_program.items {
121148
let Item::Type(decl) = item else {
@@ -156,3 +183,11 @@ fn effect_name(effect: &EffectDecl) -> &str {
156183
EffectDecl::Name(name) | EffectDecl::Retains(name) => name,
157184
}
158185
}
186+
187+
fn duplicate_symbol_label(kind: DuplicateSymbolKind) -> &'static str {
188+
match kind {
189+
DuplicateSymbolKind::Function => "function",
190+
DuplicateSymbolKind::Type => "type",
191+
DuplicateSymbolKind::Constructor => "callable",
192+
}
193+
}

src/diagnostic.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod code {
55
pub const MISSING_RETURN_TYPE: &str = "RS0002";
66
pub const MISSING_PARAMETER_TYPE: &str = "RS0003";
77
pub const UNKNOWN_EFFECT: &str = "RS0004";
8+
pub const DUPLICATE_DECLARATION: &str = "RS0005";
89
pub const FILE_MODE_VIOLATION: &str = "RS0101";
910
pub const UNNAMED_ARGUMENT: &str = "RS0201";
1011
pub const MISSING_DATA_EFFECT: &str = "RS0202";
@@ -208,6 +209,11 @@ static DIAGNOSTIC_EXPLANATIONS: &[DiagnosticExplanation] = &[
208209
title: "unknown effect",
209210
explanation: "The effect list contains an effect name outside the currently recognized MVP surface.",
210211
},
212+
DiagnosticExplanation {
213+
code: code::DUPLICATE_DECLARATION,
214+
title: "duplicate declaration",
215+
explanation: "Top-level type, constructor, and function names must be unique so symbol resolution cannot silently overwrite an earlier declaration.",
216+
},
211217
DiagnosticExplanation {
212218
code: code::FILE_MODE_VIOLATION,
213219
title: "file mode violation",

src/hir.rs

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::{HashMap, HashSet};
22

3+
use crate::diagnostic::Span;
34
use crate::syntax::ast::{
45
DataEffect, EffectDecl, FieldDecl, FunctionDecl, Item, Param, Program as SyntaxProgram,
56
TypeDecl, TypeKind,
@@ -61,21 +62,64 @@ pub struct TypeInfo {
6162
pub fields: HashMap<String, FieldInfo>,
6263
}
6364

65+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66+
pub enum DuplicateSymbolKind {
67+
Function,
68+
Type,
69+
Constructor,
70+
}
71+
72+
#[derive(Debug, Clone, PartialEq, Eq)]
73+
pub struct DuplicateSymbol {
74+
pub kind: DuplicateSymbolKind,
75+
pub name: String,
76+
pub first_span: Span,
77+
pub duplicate_span: Span,
78+
}
79+
6480
#[derive(Debug, Default)]
6581
pub struct Hir {
6682
signatures: HashMap<String, FunctionSig>,
6783
types: HashMap<String, TypeInfo>,
6884
fields_by_name: HashMap<String, Vec<FieldInfo>>,
85+
duplicate_symbols: Vec<DuplicateSymbol>,
6986
}
7087

7188
impl Hir {
7289
pub fn from_syntax(program: &SyntaxProgram) -> Self {
7390
let mut hir = Self::default();
7491
hir.insert_builtins();
92+
let mut type_symbols = HashMap::new();
93+
let mut callable_symbols = HashMap::new();
7594
for item in &program.items {
7695
match item {
77-
Item::Function(function) => hir.insert_function(function_sig_from_decl(function)),
78-
Item::Type(type_decl) => hir.insert_type(type_info_from_decl(type_decl)),
96+
Item::Function(function) => {
97+
record_duplicate_symbol(
98+
&mut hir.duplicate_symbols,
99+
&mut callable_symbols,
100+
DuplicateSymbolKind::Function,
101+
&function.name,
102+
&function.span,
103+
);
104+
hir.insert_function(function_sig_from_decl(function));
105+
}
106+
Item::Type(type_decl) => {
107+
record_duplicate_symbol(
108+
&mut hir.duplicate_symbols,
109+
&mut type_symbols,
110+
DuplicateSymbolKind::Type,
111+
&type_decl.name,
112+
&type_decl.span,
113+
);
114+
record_duplicate_symbol(
115+
&mut hir.duplicate_symbols,
116+
&mut callable_symbols,
117+
DuplicateSymbolKind::Constructor,
118+
&type_decl.name,
119+
&type_decl.span,
120+
);
121+
hir.insert_type(type_info_from_decl(type_decl));
122+
}
79123
}
80124
}
81125
hir
@@ -109,6 +153,10 @@ impl Hir {
109153
self.fields_named(field_name).any(|field| field.is_handle)
110154
}
111155

156+
pub fn duplicate_symbols(&self) -> &[DuplicateSymbol] {
157+
&self.duplicate_symbols
158+
}
159+
112160
fn insert_function(&mut self, signature: FunctionSig) {
113161
let key = match &signature.namespace {
114162
Some(namespace) => qualified_key(namespace, &signature.name),
@@ -155,6 +203,39 @@ fn function_sig_from_decl(function: &FunctionDecl) -> FunctionSig {
155203
}
156204
}
157205

206+
fn record_duplicate_symbol(
207+
duplicates: &mut Vec<DuplicateSymbol>,
208+
symbols: &mut HashMap<String, (DuplicateSymbolKind, Span)>,
209+
kind: DuplicateSymbolKind,
210+
name: &str,
211+
span: &Span,
212+
) {
213+
if let Some((first_kind, first_span)) = symbols.get(name) {
214+
duplicates.push(DuplicateSymbol {
215+
kind: duplicate_symbol_kind(*first_kind, kind),
216+
name: name.to_string(),
217+
first_span: first_span.clone(),
218+
duplicate_span: span.clone(),
219+
});
220+
return;
221+
}
222+
223+
symbols.insert(name.to_string(), (kind, span.clone()));
224+
}
225+
226+
fn duplicate_symbol_kind(
227+
first: DuplicateSymbolKind,
228+
duplicate: DuplicateSymbolKind,
229+
) -> DuplicateSymbolKind {
230+
match (first, duplicate) {
231+
(DuplicateSymbolKind::Function, DuplicateSymbolKind::Function) => {
232+
DuplicateSymbolKind::Function
233+
}
234+
(DuplicateSymbolKind::Type, DuplicateSymbolKind::Type) => DuplicateSymbolKind::Type,
235+
_ => DuplicateSymbolKind::Constructor,
236+
}
237+
}
238+
158239
fn param_sig_from_decl(param: &Param) -> ParamSig {
159240
ParamSig {
160241
name: param.name.clone(),
@@ -679,4 +760,30 @@ fn cache_put(cache: mut Cache, value: read Image) -> Unit
679760
.expect("builtin signature exists");
680761
assert_eq!(load.return_type.as_deref(), Some("Image"));
681762
}
763+
764+
#[test]
765+
fn records_duplicate_callable_symbols() {
766+
let source = r#"
767+
mode: managed
768+
769+
struct Image {
770+
pixels: Buffer
771+
}
772+
773+
fn Image(path: read Path) -> Image {
774+
}
775+
"#;
776+
777+
let program = parse_source("test.rss", source);
778+
let hir = Hir::from_syntax(&program);
779+
let duplicate = hir
780+
.duplicate_symbols()
781+
.first()
782+
.expect("constructor/function duplicate is recorded");
783+
784+
assert_eq!(duplicate.kind, DuplicateSymbolKind::Constructor);
785+
assert_eq!(duplicate.name, "Image");
786+
assert_eq!(duplicate.first_span.line, 4);
787+
assert_eq!(duplicate.duplicate_span.line, 8);
788+
}
682789
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// expect: RS0005
2+
mode: managed
3+
4+
struct Image {
5+
pixels: Buffer
6+
}
7+
8+
fn Image(path: read Path) -> Image {
9+
return Image(pixels: read path)
10+
}
11+
12+
fn Image(path: read Path) -> Image {
13+
return Image(pixels: read path)
14+
}

0 commit comments

Comments
 (0)