Repository: gleam-lang/gleam (rust)

Issue:
Language Server: Label access completion only works with public types
The language server doesn't show any suggestion for fields when a record's type isn't public:

![image](https://github.com/user-attachments/assets/3d752e6b-71b2-48e0-ad07-b2b14bf45d7e)

If we set `Person` as public, suggestions show up.

![image](https://github.com/user-attachments/assets/06dd6878-e89a-4780-bd58-e4b246e957b7)


Pull request:
Fix issue where record access completions wouldn't work for private records in the same module
Closes #3485

Looks like we were previously removing the private accessors from the environment prior to creating the ModuleInterface. This meant that even if we were in the module the type was defined in, looking up the accessor would return no result. Seeing as the accessor includes the publicity anyway, I just removed the filtering logic and did it in the lsp/inference itself instead

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ca403c17..64b5695b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,3 +11,7 @@
 ### Language Server
 
 ### Bug Fixes
+
+- Fix a bug that caused record accessors for private types to not be completed
+  by the LSP, even when in the same module.
+  ([Ameen Radwan](https://github.com/Acepie))
diff --git a/compiler-core/src/analyse.rs b/compiler-core/src/analyse.rs
index b0b61bfb1..bac174009 100644
--- a/compiler-core/src/analyse.rs
+++ b/compiler-core/src/analyse.rs
@@ -279,8 +279,6 @@ impl<'a, A> ModuleAnalyzer<'a, A> {
         // items.
         env.module_types
             .retain(|_, info| info.module == self.module_name);
-        env.accessors
-            .retain(|_, accessors| accessors.publicity.is_importable());
 
         // Ensure no exported values have private types in their type signature
         for value in env.module_values.values() {
diff --git a/compiler-core/src/language_server/completer.rs b/compiler-core/src/language_server/completer.rs
index 64c2e4277..c7d006db1 100644
--- a/compiler-core/src/language_server/completer.rs
+++ b/compiler-core/src/language_server/completer.rs
@@ -657,7 +657,8 @@ where
         match type_.as_ref() {
             Type::Named { name, module, .. } => importable_modules
                 .get(module)
-                .and_then(|i| i.accessors.get(name)),
+                .and_then(|i| i.accessors.get(name))
+                .filter(|a| a.publicity.is_importable() || module == &self.module.name),
             _ => None,
         }
     }
diff --git a/compiler-core/src/type_/expression.rs b/compiler-core/src/type_/expression.rs
index 7c81f183f..9a967ca31 100644
--- a/compiler-core/src/type_/expression.rs
+++ b/compiler-core/src/type_/expression.rs
@@ -2206,7 +2206,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
                 .environment
                 .importable_modules
                 .get(module)
-                .and_then(|module| module.accessors.get(name)),
+                .and_then(|module| module.accessors.get(name))
+                .filter(|a| {
+                    a.publicity.is_importable() || module == &self.environment.current_module
+                }),
 
             _something_without_fields => return Err(unknown_field(vec![])),
         }


Test patch:
diff --git a/compiler-core/src/language_server/tests/completion.rs b/compiler-core/src/language_server/tests/completion.rs
index 679712da7..df560a271 100644
--- a/compiler-core/src/language_server/tests/completion.rs
+++ b/compiler-core/src/language_server/tests/completion.rs
@@ -1275,6 +1275,23 @@ fn fun() {
     assert_completion!(TestProject::for_source(code), Position::new(8, 15));
 }
 
+#[test]
+fn completions_for_private_record_access() {
+    let code = "
+type Wibble {
+  Wibble(wibble: Int, wobble: Int)
+  Wobble(wabble: Int, wobble: Int)
+}
+
+fn fun() {
+  let wibble = Wibble(1, 2)
+  wibble.wobble
+}
+";
+
+    assert_completion!(TestProject::for_source(code), Position::new(8, 15));
+}
+
 #[test]
 fn completions_for_record_labels() {
     let code = "
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_private_record_access.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_private_record_access.snap
new file mode 100644
index 000000000..df00a04c9
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_private_record_access.snap
@@ -0,0 +1,44 @@
+---
+source: compiler-core/src/language_server/tests/completion.rs
+expression: "\ntype Wibble {\n  Wibble(wibble: Int, wobble: Int)\n  Wobble(wabble: Int, wobble: Int)\n}\n\nfn fun() {\n  let wibble = Wibble(1, 2)\n  wibble.wobble\n}\n"
+---
+type Wibble {
+  Wibble(wibble: Int, wobble: Int)
+  Wobble(wabble: Int, wobble: Int)
+}
+
+fn fun() {
+  let wibble = Wibble(1, 2)
+  wibble.wobble|
+}
+
+
+----- Completion content -----
+[
+    CompletionItem {
+        label: "wobble",
+        label_details: None,
+        kind: Some(
+            Field,
+        ),
+        detail: Some(
+            "Int",
+        ),
+        documentation: None,
+        deprecated: None,
+        preselect: None,
+        sort_text: Some(
+            "1_wobble",
+        ),
+        filter_text: None,
+        insert_text: None,
+        insert_text_format: None,
+        insert_text_mode: None,
+        text_edit: None,
+        additional_text_edits: None,
+        command: None,
+        commit_characters: None,
+        data: None,
+        tags: None,
+    },
+]