Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: report error on duplicate let-binding within same scope #75

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
3 │ fn foo() {
4 │ let zoo = 0;
· ──────┬─────
· ╰── previously declared here
5 │ let zoo = 1;
· ──────┬─────
· ╰── the binding zoo shadows an existing binding with the same name
6 │ }
╰────
help: give this binding a different name