Repository: gleam-lang/gleam (rust)

Issue:
Target support not checked for unused unqualified imported values
Example:

```rust
import gleam/erlang.{rescue}
import gleam/io

pub fn main() {
    io.println("Hello, Joe!")
}
```

Generates an import in the JS module to `gleam/erlang.{rescue}`, even though this value is never used:

```bash
$ gleam run --target javascript
  ┌─ /home/arkan/Projects/probe/gleam/app/src/app.gleam:1:22
  │
1 │ import gleam/erlang.{rescue}
  │                      ^^^^^^ This imported value is never used

Hint: You can safely remove it.
   Compiled in 0.04s
    Running app.main
file:///home/arkan/Projects/probe/gleam/app/build/dev/javascript/app/app.mjs:2
import { rescue } from "../gleam_erlang/gleam/erlang.mjs";
         ^^^^^^
SyntaxError: The requested module '../gleam_erlang/gleam/erlang.mjs' does not provide an export named 'rescue'
    at ModuleJob._instantiate (node:internal/modules/esm/module_job:134:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:217:5)
    at async ModuleLoader.import (node:internal/modules/esm/loader:323:24)
    at async loadESM (node:internal/process/esm_loader:28:7)
    at async handleMainPromise (node:internal/modules/run_main:113:12)

Node.js v20.12.2

$ gleam -V
gleam 1.2.1
```

I noticed this in practice when trying to compile Wisp to Javascript. Wisp depends on Mist for `mist_handler`, which in turn imports `gleam/erlang.{rescue}` unqualified. I never called erlang-specific functions; instead I wanted to experiment whether or not you _could_ use Wisp as a framework for serverless functions, since its `Request -> Response` structure is really similar to what you are expected to expose from Javascript.

~joshi :purple_heart: 

Pull request:
Check target support when a value is imported
Fixes #3371 
We now check the target support of a value when it is imported, as well as when it is used.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c7e3620a5..eb9f45895 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -330,6 +330,10 @@
   inexhaustive case expressions matching on more than one subject.
   ([Surya Rose](https://github.com/GearsDatapacks))
 
+- Fixed a bug where the compiler would not check the target support of a function
+  if it was imported and not used, and generate invalid code.
+  ([Surya Rose](https://github.com/GearsDatapacks))
+
 ## v1.4.1 - 2024-08-04
 
 ### Bug Fixes
diff --git a/compiler-core/src/analyse/imports.rs b/compiler-core/src/analyse/imports.rs
index 212c2a908..930945093 100644
--- a/compiler-core/src/analyse/imports.rs
+++ b/compiler-core/src/analyse/imports.rs
@@ -119,6 +119,17 @@ impl<'context, 'problems> Importer<'context, 'problems> {
         // Register the unqualified import if it is a value
         let variant = match module.get_public_value(import_name) {
             Some(value) => {
+                let implementations = value.variant.implementations();
+                // Check the target support of the imported value
+                if self.environment.target_support.is_enforced()
+                    && !implementations.supports(self.environment.target)
+                {
+                    self.problems.error(Error::UnsupportedExpressionTarget {
+                        target: self.environment.target,
+                        location,
+                    })
+                }
+
                 self.environment.insert_variable(
                     used_name.clone(),
                     value.variant.clone(),


Test patch:
diff --git a/compiler-core/src/type_/tests/externals.rs b/compiler-core/src/type_/tests/externals.rs
index 9afdcd226..ad7a14918 100644
--- a/compiler-core/src/type_/tests/externals.rs
+++ b/compiler-core/src/type_/tests/externals.rs
@@ -1,6 +1,6 @@
 use crate::{
-    assert_js_module_error, assert_js_module_infer, assert_module_error, assert_module_infer,
-    assert_with_module_error,
+    assert_infer_with_module, assert_js_module_error, assert_js_module_infer, assert_module_error,
+    assert_module_infer, assert_with_module_error,
 };
 
 // https://github.com/gleam-lang/gleam/issues/2324
@@ -181,7 +181,7 @@ fn javascript_only_constant() {
             r#"@external(javascript, "one", "two")
 fn javascript_only() -> Int
 const constant = javascript_only
-pub const javascript_only_constant = constant 
+pub const javascript_only_constant = constant
 "#
         ),
         "import module
@@ -208,3 +208,29 @@ pub fn main() -> Int
     assert_module_infer!(module, vec![("main", "fn() -> Int")]);
     assert_js_module_error!(module);
 }
+
+#[test]
+fn unsupported_target_for_unused_import() {
+    // If we import a function which doesn't support the current target,
+    // even if we don't use it, the compiler should error
+    assert_with_module_error!(
+        (
+            "mod",
+            r#"@external(javascript, "wibble", "wobble") pub fn wobble()"#
+        ),
+        "import mod.{wobble}"
+    );
+}
+
+#[test]
+fn supported_target_for_imported_value() {
+    assert_infer_with_module!(
+        (
+            "mod",
+            r#"@external(erlang, "wibble", "wobble") pub fn wobble() -> Int"#
+        ),
+        "import mod.{wobble}
+pub const wobble = wobble",
+        vec![("wobble", "fn() -> Int")],
+    );
+}
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__unsupported_target_for_unused_import.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__unsupported_target_for_unused_import.snap
new file mode 100644
index 000000000..dcaa35cda
--- /dev/null
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__unsupported_target_for_unused_import.snap
@@ -0,0 +1,14 @@
+---
+source: compiler-core/src/type_/tests/externals.rs
+expression: "import mod.{wobble}"
+---
+error: Unsupported target
+  ┌─ /src/one/two.gleam:1:13
+  │
+1 │ import mod.{wobble}
+  │             ^^^^^^
+
+This value is not available as it is defined using externals, and there is
+no implementation for the Erlang target.
+
+Hint: Did you mean to build for a different target?