Skip to content

Warn when top-level definition shadows imported value #4672

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

Merged
merged 8 commits into from
Jun 16, 2025
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@

([Zij-IT](https://github.com/zij-it))

- The compiler now emits a warning when a top-level constant or function declaration
shadows an imported name in the current module.
([Aayush Tripathi](https://github.com/aayush-tripathi))

### Build tool

- `gleam update`, `gleam deps update`, and `gleam deps download` will now print
Expand Down
19 changes: 19 additions & 0 deletions compiler-core/src/analyse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,8 @@ impl<'a, A> ModuleAnalyzer<'a, A> {
..
} = c;
self.check_name_case(name_location, &name, Named::Constant);
// If the constant's name matches an unqualified import, emit a warning:
self.check_shadow_import(&name, c.location, environment);

environment.references.begin_constant();

Expand Down Expand Up @@ -1422,6 +1424,8 @@ impl<'a, A> ModuleAnalyzer<'a, A> {
let (name_location, name) = name.as_ref().expect("A module's function must be named");

self.check_name_case(*name_location, name, Named::Function);
// If the function's name matches an unqualified import, emit a warning:
self.check_shadow_import(name, f.location, environment);

environment.references.register_value(
name.clone(),
Expand Down Expand Up @@ -1551,6 +1555,21 @@ impl<'a, A> ModuleAnalyzer<'a, A> {
self.minimum_required_version = minimum_required_version;
}
}

fn check_shadow_import(
&mut self,
name: &EcoString,
location: SrcSpan,
environment: &mut Environment<'_>,
) {
if environment.unqualified_imported_names.contains_key(name) {
self.problems
.warning(Warning::TopLevelDefinitionShadowsImport {
location,
name: name.clone(),
});
}
}
}

fn optionally_push<T>(vector: &mut Vec<T>, item: Option<T>) {
Expand Down
8 changes: 8 additions & 0 deletions compiler-core/src/type_/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,13 @@ pub enum Warning {
first: SrcSpan,
second: SrcSpan,
},

/// Top-level definition should not shadow an imported one.
/// This includes constant or function imports.
TopLevelDefinitionShadowsImport {
location: SrcSpan,
name: EcoString,
},
}

#[derive(Debug, Eq, Copy, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -1240,6 +1247,7 @@ impl Warning {
| Warning::JavaScriptIntUnsafe { location, .. }
| Warning::AssertLiteralValue { location, .. }
| Warning::BitArraySegmentTruncatedValue { location, .. }
| Warning::TopLevelDefinitionShadowsImport { location, .. }
| Warning::ModuleImportedTwice {
second: location, ..
} => *location,
Expand Down
2 changes: 1 addition & 1 deletion compiler-core/src/type_/tests/dead_code_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ pub const wibble = 2

#[test]
fn used_shadowed_imported_value() {
assert_no_warnings!(
assert_warning!(
(
"thepackage",
"wibble",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
source: compiler-core/src/type_/tests/dead_code_detection.rs
assertion_line: 431
expression: "\nimport wibble.{wibble}\n\npub const wibble = 2\n"
snapshot_kind: text
---
----- SOURCE CODE
-- wibble.gleam
Expand All @@ -23,3 +25,13 @@ warning: Unused imported module
│ ^^^^^^^^^^^^^^^^^^^^^^ This imported module is never used

Hint: You can safely remove it.

warning: Shadowed Import
┌─ /src/warning/wrn.gleam:4:1
4 │ pub const wibble = 2
│ ^^^^^^^^^^^^^^^^ `wibble` is defined here

Definition of wibble shadows an imported value.
The imported value could not be used in this module anyway.
Hint: Either rename the definition or remove the import.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
source: compiler-core/src/type_/tests/dead_code_detection.rs
assertion_line: 448
expression: "\nimport wibble.{wibble}\n\npub const wibble = wibble\n"
snapshot_kind: text
---
----- SOURCE CODE
-- wibble.gleam

pub const wibble = 1


-- main.gleam

import wibble.{wibble}

pub const wibble = wibble


----- WARNING
warning: Shadowed Import
┌─ /src/warning/wrn.gleam:4:1
4 │ pub const wibble = wibble
│ ^^^^^^^^^^^^^^^^ `wibble` is defined here

Definition of wibble shadows an imported value.
The imported value could not be used in this module anyway.
Hint: Either rename the definition or remove the import.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
source: compiler-core/src/type_/tests/warnings.rs
assertion_line: 3950
expression: "\nimport module.{value}\n\npub const value = 1\n"
snapshot_kind: text
---
----- SOURCE CODE
-- module.gleam

pub const value = 1


-- main.gleam

import module.{value}

pub const value = 1


----- WARNING
warning: Unused imported module
┌─ /src/warning/wrn.gleam:2:1
2 │ import module.{value}
│ ^^^^^^^^^^^^^^^^^^^^^ This imported module is never used

Hint: You can safely remove it.

warning: Shadowed Import
┌─ /src/warning/wrn.gleam:4:1
4 │ pub const value = 1
│ ^^^^^^^^^^^^^^^ `value` is defined here

Definition of value shadows an imported value.
The imported value could not be used in this module anyway.
Hint: Either rename the definition or remove the import.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
source: compiler-core/src/type_/tests/warnings.rs
assertion_line: 3932
expression: "\nimport module.{wibble}\n\npub fn wibble() { Nil }\n"
snapshot_kind: text
---
----- SOURCE CODE
-- module.gleam

pub fn wibble() { Nil }


-- main.gleam

import module.{wibble}

pub fn wibble() { Nil }


----- WARNING
warning: Unused imported module
┌─ /src/warning/wrn.gleam:2:1
2 │ import module.{wibble}
│ ^^^^^^^^^^^^^^^^^^^^^^ This imported module is never used

Hint: You can safely remove it.

warning: Shadowed Import
┌─ /src/warning/wrn.gleam:4:1
4 │ pub fn wibble() { Nil }
│ ^^^^^^^^^^^^^^^ `wibble` is defined here

Definition of wibble shadows an imported value.
The imported value could not be used in this module anyway.
Hint: Either rename the definition or remove the import.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
source: compiler-core/src/type_/tests/warnings.rs
assertion_line: 974
expression: "\n import gleam/wibble.{one} as wobble\n pub const one = one\n "
snapshot_kind: text
---
----- SOURCE CODE
-- gleam/wibble.gleam
Expand All @@ -22,3 +24,14 @@ warning: Unused imported module alias
Hint: You can safely remove it.

import gleam/wibble as _


warning: Shadowed Import
┌─ /src/warning/wrn.gleam:3:13
3 │ pub const one = one
│ ^^^^^^^^^^^^^ `one` is defined here

Definition of one shadows an imported value.
The imported value could not be used in this module anyway.
Hint: Either rename the definition or remove the import.
36 changes: 36 additions & 0 deletions compiler-core/src/type_/tests/warnings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3926,3 +3926,39 @@ pub fn main() {
"
);
}
//https://github.com/gleam-lang/gleam/issues/4666
#[test]
fn shadow_imported_function() {
assert_warning!(
(
"thepackage",
"module",
r#"
pub fn wibble() { Nil }
"#
),
r#"
import module.{wibble}

pub fn wibble() { Nil }
"#
);
}
//https://github.com/gleam-lang/gleam/issues/4666
#[test]
fn shadow_imported_constant() {
assert_warning!(
(
"thepackage",
"module",
r#"
pub const value = 1
"#
),
r#"
import module.{value}

pub const value = 1
"#
);
}
23 changes: 23 additions & 0 deletions compiler-core/src/warning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,29 @@ can already tell whether it will be true or false.",
}],
}),
},

type_::Warning::TopLevelDefinitionShadowsImport { location, name } => {
let text = format!(
"Definition of {} shadows an imported value.
The imported value could not be used in this module anyway.",
name
);
Diagnostic {
title: "Shadowed Import".into(),
text: wrap(&text),
level: diagnostic::Level::Warning,
location: Some(Location {
path: path.clone(),
src: src.clone(),
label: diagnostic::Label {
text: Some(wrap(&format!("`{}` is defined here", name))),
span: *location,
},
extra_labels: Vec::new(),
}),
hint: Some("Either rename the definition or remove the import.".into()),
}
}
},

Warning::DeprecatedEnvironmentVariable { variable } => {
Expand Down
Loading