Skip to content

Commit

Permalink
fix: report error on duplicate let-binding within same scope
Browse files Browse the repository at this point in the history
Fixes #71
  • Loading branch information
junlarsen committed Feb 15, 2025
1 parent 2cb2fa7 commit 37a8b44
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use crate::passes::hir_type_check_pass::{
use crate::scope::Scope;
use crate::HirResult;
use eight_support::errors::hir::{
BindingReDeclaresName, ConstructingNonStructTypeError, ConstructingPointerTypeError,
DereferenceOfNonPointerError, FunctionTypeMismatchError, HirError,
ConstructingNonStructTypeError, ConstructingPointerTypeError, DereferenceOfNonPointerError,
DuplicateLetBindingInSameScopeError, FunctionTypeMismatchError, HirError,
InvalidFieldReferenceOfNonStructError, InvalidStructFieldReferenceError, MissingFieldError,
SelfReferentialTypeError, TraitDoesNotExistError, TraitInstanceMissingFnError,
TraitMethodDoesNotExistError, TraitMissingInstanceError, TypeMismatchError,
Expand Down Expand Up @@ -45,7 +45,7 @@ pub struct TypingContext<'hir> {
/// We currently don't support nested functions or lambdas, so this does not necessarily have to
/// be a VecDeque, but it's here for future use.
type_binding_context: Scope<&'hir str, &'hir HirTy<'hir>>,
let_binding_context: Scope<&'hir str, &'hir HirTy<'hir>>,
let_binding_context: Scope<&'hir str, (&'hir HirTy<'hir>, Span)>,
// TODO: make private
pub type_parameter_instantiations: Scope<(u32, u32), &'hir HirTy<'hir>>,
/// Track the current function for type checking against expected return types.
Expand Down Expand Up @@ -145,20 +145,24 @@ impl<'hir> TypingContext<'hir> {
ty: &'hir HirTy<'hir>,
) -> HirResult<()> {
let current_depth = self.let_binding_context.depth();
if let Some((depth, _)) = self.let_binding_context.find_with_depth(&name) {
if depth >= current_depth {
return Err(HirError::BindingReDeclaresName(BindingReDeclaresName {
if let Some((_, definition)) = self
.let_binding_context
.find_within_depth(&name, current_depth)
{
return Err(HirError::DuplicateLetBindingInSameScope(
DuplicateLetBindingInSameScopeError {
previous: *definition,
name: name.to_owned(),
span,
}));
}
},
));
}
self.let_binding_context.add(name, ty);
self.let_binding_context.add(name, (ty, span));
Ok(())
}

/// Find the type of a let binding.
pub fn find_let_binding(&self, name: &'hir str) -> Option<&'hir HirTy<'hir>> {
pub fn find_let_binding(&self, name: &'hir str) -> Option<(&'hir HirTy<'hir>, Span)> {
self.let_binding_context.find(&name).copied()
}

Expand Down Expand Up @@ -264,7 +268,7 @@ impl<'hir> TypingContext<'hir> {
let HirReferenceSymbol::Local(local) = &mut expr.kind else {
ice!("called infer() on a name that doesn't exist in the context");
};
let Some(local_ty) = self.find_let_binding(local.name) else {
let Some((local_ty, _)) = self.find_let_binding(local.name) else {
ice!("called infer() on a name that doesn't exist in the context");
};
self.constrain_eq(expectation, local_ty, expr.span, local.name_span);
Expand Down
9 changes: 9 additions & 0 deletions compiler/eight-middle/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ impl<K: Ord, V> Scope<K, V> {
None
}

pub fn find_within_depth(&self, name: &K, depth: usize) -> Option<&V> {
for (index, scope) in self.scopes.iter().rev().enumerate() {
if index == depth - 1 {
return scope.get(name);
}
}
None
}

/// Find an item in the context, and return the distance from the root scope.
pub fn find_with_depth(&self, name: &K) -> Option<(usize, &V)> {
for (depth, scope) in self.scopes.iter().enumerate() {
Expand Down
10 changes: 6 additions & 4 deletions compiler/eight-support/src/errors/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ declare_error_type! {
TraitMissingInstance(TraitMissingInstanceError),
WrongTraitTypeArgumentCount(WrongTraitTypeArgumentCount),
TypeParameterShadowsExisting(TypeParameterShadowsExisting),
BindingReDeclaresName(BindingReDeclaresName),
DuplicateLetBindingInSameScope(DuplicateLetBindingInSameScopeError),
ConstructingNonStructType(ConstructingNonStructTypeError),
ConstructingPointerType(ConstructingPointerTypeError),
WrongFunctionTypeArgumentCount(WrongFunctionTypeArgumentCount),
Expand Down Expand Up @@ -233,12 +233,14 @@ pub struct TypeParameterShadowsExisting {

#[derive(Error, Diagnostic, Debug)]
#[diagnostic(
code(sema::binding_re_declares_name),
code(sema::duplicate_let_binding_in_scope),
help("give this binding a different name")
)]
#[error("binding {name} re-declares name")]
pub struct BindingReDeclaresName {
#[error("binding {name} has already been declared in this scope")]
pub struct DuplicateLetBindingInSameScopeError {
pub name: String,
#[label = "previously declared here"]
pub previous: Span,
#[label = "the binding {name} shadows an existing binding with the same name"]
pub span: Span,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,4 @@ fn test<T, U>(x: T) -> T {
let z = generic_with_gen(1, true);
let v: T = x;
// CHECK: let v: [[TYPE_VAR_T]]
let z: U = spawn();
// CHECK: let z: [[TYPE_VAR_U]]
}
6 changes: 6 additions & 0 deletions tests/ui/hir/duplicate-let-binding-in-same-scope.eight
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// RUN: not %eightc %s 2>&1 | %regtest test %s

fn foo() {
let zoo = 0;
let zoo = 1;
}
14 changes: 14 additions & 0 deletions tests/ui/hir/duplicate-let-binding-in-same-scope.eight.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Error: sema::duplicate_let_binding_in_scope

× binding zoo has already been declared in this scope
╭─[duplicate-let-binding-in-same-scope.eight:4:3]
3fn foo() {
4let zoo = 0;
· ──────┬─────
· ╰── previously declared here
5let zoo = 1;
· ──────┬─────
· ╰── the binding zoo shadows an existing binding with the same name
6 │ }
╰────
help: give this binding a different name

0 comments on commit 37a8b44

Please sign in to comment.