diff --git a/CHANGELOG.md b/CHANGELOG.md index 6861fba317b..5fbf63a1f95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -206,6 +206,10 @@ error message instead of silently doing nothing. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) +- When providing autocomplete suggestions, the language server will now + prioritise values which match the expected type of the value being completed. + ([Surya Rose](https://github.com/GearsDatapacks)) + ### Formatter ### Bug fixes diff --git a/compiler-core/src/language_server/completer.rs b/compiler-core/src/language_server/completer.rs index 03cb671f860..34b5a5a44e3 100644 --- a/compiler-core/src/language_server/completer.rs +++ b/compiler-core/src/language_server/completer.rs @@ -51,10 +51,17 @@ enum CompletionKind { ImportableModule, } +#[derive(Copy, Clone)] +enum TypeMatch { + Matching, + Incompatible, + Unknown, +} + // Gives the sort text for a completion item based on the kind and label. // This ensures that more specific kinds of completions are placed before // less specific ones.. -fn sort_text(kind: CompletionKind, label: &str) -> String { +fn sort_text(kind: CompletionKind, label: &str, type_match: TypeMatch) -> String { let priority: u8 = match kind { CompletionKind::Label => 0, CompletionKind::FieldAccessor => 1, @@ -63,7 +70,12 @@ fn sort_text(kind: CompletionKind, label: &str) -> String { CompletionKind::Prelude => 4, CompletionKind::ImportableModule => 5, }; - format!("{priority}_{label}") + match type_match { + // We want to prioritise type which match what is expected in the completion + // as those are more likely to be what the user wants. + TypeMatch::Matching => format!("0{priority}_{label}"), + TypeMatch::Incompatible | TypeMatch::Unknown => format!("{priority}_{label}"), + } } // The form in which a type completion is needed in context. @@ -91,6 +103,11 @@ pub struct Completer<'a, IO> { /// This is not necessarily the same as src_line_numbers if the module /// is in a non-compiling state pub module_line_numbers: LineNumbers, + + /// The expected type of the value we are completing. `None` if we are + /// completing a type annotation or label, where this information is not + /// applicable. + pub expected_type: Option>, } impl<'a, IO> Completer<'a, IO> { @@ -107,6 +124,7 @@ impl<'a, IO> Completer<'a, IO> { compiler, module, module_line_numbers: LineNumbers::new(&module.code), + expected_type: None, } } @@ -296,7 +314,7 @@ impl<'a, IO> Completer<'a, IO> { if already_imported_values.contains(name) { continue; } - completions.push(value_completion( + completions.push(self.value_completion( None, &module_being_imported_from.name, name, @@ -415,7 +433,11 @@ impl<'a, IO> Completer<'a, IO> { for type_ in PreludeType::iter() { let label: String = type_.name().into(); - let sort_text = Some(sort_text(CompletionKind::Prelude, &label)); + let sort_text = Some(sort_text( + CompletionKind::Prelude, + &label, + TypeMatch::Unknown, + )); completions.push(CompletionItem { label, detail: Some("Type".into()), @@ -564,7 +586,13 @@ impl<'a, IO> Completer<'a, IO> { .peek() { completions.extend( - LocalCompletion::new(mod_name, insert_range, cursor).fn_completions(function), + LocalCompletion::new( + mod_name, + insert_range, + cursor, + self.expected_type.clone(), + ) + .fn_completions(function), ); } @@ -572,7 +600,7 @@ impl<'a, IO> Completer<'a, IO> { // Here we do not check for the internal attribute: we always want // to show autocompletions for values defined in the same module, // even if those are internal. - completions.push(value_completion( + completions.push(self.value_completion( None, mod_name, name, @@ -582,9 +610,13 @@ impl<'a, IO> Completer<'a, IO> { )); } - let mut push_prelude_completion = |label: &str, kind| { + let mut push_prelude_completion = |label: &str, kind, type_: Arc| { let label = label.to_string(); - let sort_text = Some(sort_text(CompletionKind::Prelude, &label)); + let sort_text = Some(sort_text( + CompletionKind::Prelude, + &label, + match_type(&self.expected_type, &type_), + )); completions.push(CompletionItem { label, detail: Some(PRELUDE_MODULE_NAME.into()), @@ -597,15 +629,35 @@ impl<'a, IO> Completer<'a, IO> { for type_ in PreludeType::iter() { match type_ { PreludeType::Bool => { - push_prelude_completion("True", CompletionItemKind::ENUM_MEMBER); - push_prelude_completion("False", CompletionItemKind::ENUM_MEMBER); + push_prelude_completion( + "True", + CompletionItemKind::ENUM_MEMBER, + type_::bool(), + ); + push_prelude_completion( + "False", + CompletionItemKind::ENUM_MEMBER, + type_::bool(), + ); } PreludeType::Nil => { - push_prelude_completion("Nil", CompletionItemKind::ENUM_MEMBER); + push_prelude_completion( + "Nil", + CompletionItemKind::ENUM_MEMBER, + type_::nil(), + ); } PreludeType::Result => { - push_prelude_completion("Ok", CompletionItemKind::CONSTRUCTOR); - push_prelude_completion("Error", CompletionItemKind::CONSTRUCTOR); + push_prelude_completion( + "Ok", + CompletionItemKind::CONSTRUCTOR, + type_::result(type_::unbound_var(0), type_::unbound_var(0)), + ); + push_prelude_completion( + "Error", + CompletionItemKind::CONSTRUCTOR, + type_::result(type_::unbound_var(0), type_::unbound_var(0)), + ); } PreludeType::BitArray | PreludeType::Float @@ -639,7 +691,7 @@ impl<'a, IO> Completer<'a, IO> { { continue; } - completions.push(value_completion( + completions.push(self.value_completion( Some(&module), mod_name, name, @@ -657,7 +709,7 @@ impl<'a, IO> Completer<'a, IO> { for unqualified in &import.unqualified_values { if let Some(value) = module.get_public_value(&unqualified.name) { let name = unqualified.used_name(); - completions.push(value_completion( + completions.push(self.value_completion( None, mod_name, name, @@ -705,7 +757,7 @@ impl<'a, IO> Completer<'a, IO> { continue; } - let mut completion = value_completion( + let mut completion = self.value_completion( Some(qualifier), module_full_name, name, @@ -759,7 +811,7 @@ impl<'a, IO> Completer<'a, IO> { .map(|accessors| { accessors .values() - .map(|accessor| field_completion(&accessor.label, accessor.type_.clone())) + .map(|accessor| self.field_completion(&accessor.label, accessor.type_.clone())) .collect_vec() }) .unwrap_or_default() @@ -811,7 +863,7 @@ impl<'a, IO> Completer<'a, IO> { .map(|argument| Printer::new().pretty_print(argument, 0)) }); let label = format!("{label}:"); - let sort_text = Some(sort_text(CompletionKind::Label, &label)); + let sort_text = Some(sort_text(CompletionKind::Label, &label, TypeMatch::Unknown)); CompletionItem { label, detail, @@ -841,6 +893,70 @@ impl<'a, IO> Completer<'a, IO> { Publicity::Public => true, } } + + fn value_completion( + &self, + module_qualifier: Option<&str>, + module_name: &str, + name: &str, + value: &type_::ValueConstructor, + insert_range: Range, + priority: CompletionKind, + ) -> CompletionItem { + let type_match = match_type(&self.expected_type, &value.type_); + let label = match module_qualifier { + Some(module) => format!("{module}.{name}"), + None => name.to_string(), + }; + + let type_ = Printer::new().pretty_print(&value.type_, 0); + + let kind = Some(match value.variant { + ValueConstructorVariant::LocalVariable { .. } => CompletionItemKind::VARIABLE, + ValueConstructorVariant::ModuleConstant { .. } => CompletionItemKind::CONSTANT, + ValueConstructorVariant::LocalConstant { .. } => CompletionItemKind::CONSTANT, + ValueConstructorVariant::ModuleFn { .. } => CompletionItemKind::FUNCTION, + ValueConstructorVariant::Record { arity: 0, .. } => CompletionItemKind::ENUM_MEMBER, + ValueConstructorVariant::Record { .. } => CompletionItemKind::CONSTRUCTOR, + }); + + let documentation = value.get_documentation().map(|d| { + Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: d.into(), + }) + }); + + CompletionItem { + label: label.clone(), + kind, + detail: Some(type_), + label_details: Some(CompletionItemLabelDetails { + detail: None, + description: Some(module_name.into()), + }), + documentation, + sort_text: Some(sort_text(priority, &label, type_match)), + text_edit: Some(CompletionTextEdit::Edit(TextEdit { + range: insert_range, + new_text: label.clone(), + })), + ..Default::default() + } + } + + fn field_completion(&self, label: &str, type_: Arc) -> CompletionItem { + let type_match = match_type(&self.expected_type, &type_); + let type_ = Printer::new().pretty_print(&type_, 0); + + CompletionItem { + label: label.into(), + kind: Some(CompletionItemKind::FIELD), + detail: Some(type_), + sort_text: Some(sort_text(CompletionKind::FieldAccessor, label, type_match)), + ..Default::default() + } + } } fn add_import_to_completion( @@ -879,7 +995,7 @@ fn type_completion( label: label.clone(), kind, detail: Some("Type".into()), - sort_text: Some(sort_text(priority, &label)), + sort_text: Some(sort_text(priority, &label, TypeMatch::Unknown)), text_edit: Some(CompletionTextEdit::Edit(TextEdit { range: insert_range, new_text: match include_type_in_completion { @@ -892,96 +1008,28 @@ fn type_completion( } } -fn value_completion( - module_qualifier: Option<&str>, - module_name: &str, - name: &str, - value: &type_::ValueConstructor, - insert_range: Range, - priority: CompletionKind, -) -> CompletionItem { - let label = match module_qualifier { - Some(module) => format!("{module}.{name}"), - None => name.to_string(), - }; - - let type_ = Printer::new().pretty_print(&value.type_, 0); - - let kind = Some(match value.variant { - ValueConstructorVariant::LocalVariable { .. } => CompletionItemKind::VARIABLE, - ValueConstructorVariant::ModuleConstant { .. } => CompletionItemKind::CONSTANT, - ValueConstructorVariant::LocalConstant { .. } => CompletionItemKind::CONSTANT, - ValueConstructorVariant::ModuleFn { .. } => CompletionItemKind::FUNCTION, - ValueConstructorVariant::Record { arity: 0, .. } => CompletionItemKind::ENUM_MEMBER, - ValueConstructorVariant::Record { .. } => CompletionItemKind::CONSTRUCTOR, - }); - - let documentation = value.get_documentation().map(|d| { - Documentation::MarkupContent(MarkupContent { - kind: MarkupKind::Markdown, - value: d.into(), - }) - }); - - CompletionItem { - label: label.clone(), - kind, - detail: Some(type_), - label_details: Some(CompletionItemLabelDetails { - detail: None, - description: Some(module_name.into()), - }), - documentation, - sort_text: Some(sort_text(priority, &label)), - text_edit: Some(CompletionTextEdit::Edit(TextEdit { - range: insert_range, - new_text: label.clone(), - })), - ..Default::default() - } -} - -fn local_value_completion( - module_name: &str, - name: &str, - type_: Arc, - insert_range: Range, -) -> CompletionItem { - let label = name.to_string(); - let type_ = Printer::new().pretty_print(&type_, 0); - - let documentation = Documentation::MarkupContent(MarkupContent { - kind: MarkupKind::Markdown, - value: String::from("A locally defined variable."), - }); - - CompletionItem { - label: label.clone(), - kind: Some(CompletionItemKind::VARIABLE), - detail: Some(type_), - label_details: Some(CompletionItemLabelDetails { - detail: None, - description: Some(module_name.into()), - }), - documentation: Some(documentation), - sort_text: Some(sort_text(CompletionKind::LocallyDefined, &label)), - text_edit: Some(CompletionTextEdit::Edit(TextEdit { - range: insert_range, - new_text: label.clone(), - })), - ..Default::default() - } -} - -fn field_completion(label: &str, type_: Arc) -> CompletionItem { - let type_ = Printer::new().pretty_print(&type_, 0); - - CompletionItem { - label: label.into(), - kind: Some(CompletionItemKind::FIELD), - detail: Some(type_), - sort_text: Some(sort_text(CompletionKind::FieldAccessor, label)), - ..Default::default() +fn match_type(expected_type: &Option>, type_: &Type) -> TypeMatch { + if let Some(expected_type) = expected_type { + // If the type of the value we are completing is unbound, that + // technically means that all types match, which doesn't give us + // any useful information so we treat it as not knowing what the + // type is, which generally is the case. + if expected_type.is_unbound() { + TypeMatch::Unknown + } + // We also want to prioritise functions which return the desired type, + // as often the user's intention will be to write a function call. + else if let Some((_, return_)) = type_.fn_types() + && expected_type.same_as(&return_) + { + TypeMatch::Matching + } else if expected_type.same_as(type_) { + TypeMatch::Matching + } else { + TypeMatch::Incompatible + } + } else { + TypeMatch::Unknown } } @@ -990,15 +1038,22 @@ pub struct LocalCompletion<'a> { insert_range: Range, cursor: u32, completions: HashMap, + expected_type: Option>, } impl<'a> LocalCompletion<'a> { - pub fn new(mod_name: &'a str, insert_range: Range, cursor: u32) -> Self { + pub fn new( + mod_name: &'a str, + insert_range: Range, + cursor: u32, + expected_type: Option>, + ) -> Self { Self { mod_name, insert_range, cursor, completions: HashMap::new(), + expected_type, } } @@ -1035,9 +1090,48 @@ impl<'a> LocalCompletion<'a> { _ = self.completions.insert( name.clone(), - local_value_completion(self.mod_name, name, type_, self.insert_range), + self.local_value_completion(self.mod_name, name, type_, self.insert_range), ); } + + fn local_value_completion( + &self, + module_name: &str, + name: &str, + type_: Arc, + insert_range: Range, + ) -> CompletionItem { + let type_match = match_type(&self.expected_type, &type_); + + let label = name.to_string(); + let type_ = Printer::new().pretty_print(&type_, 0); + + let documentation = Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: String::from("A locally defined variable."), + }); + + CompletionItem { + label: label.clone(), + kind: Some(CompletionItemKind::VARIABLE), + detail: Some(type_), + label_details: Some(CompletionItemLabelDetails { + detail: None, + description: Some(module_name.into()), + }), + documentation: Some(documentation), + sort_text: Some(sort_text( + CompletionKind::LocallyDefined, + &label, + type_match, + )), + text_edit: Some(CompletionTextEdit::Edit(TextEdit { + range: insert_range, + new_text: label.clone(), + })), + ..Default::default() + } + } } impl<'ast> Visit<'ast> for LocalCompletion<'_> { diff --git a/compiler-core/src/language_server/engine.rs b/compiler-core/src/language_server/engine.rs index 438c0eac99d..9498a3965f7 100644 --- a/compiler-core/src/language_server/engine.rs +++ b/compiler-core/src/language_server/engine.rs @@ -271,7 +271,7 @@ where None => return Ok(None), }; - let completer = Completer::new(&src, ¶ms, &this.compiler, module); + let mut completer = Completer::new(&src, ¶ms, &this.compiler, module); let byte_index = completer.module_line_numbers.byte_index(params.position); // If in comment context, do not provide completions @@ -279,7 +279,7 @@ where return Ok(None); } - // Check current filercontents if the user is writing an import + // Check current file contents if the user is writing an import // and handle separately from the rest of the completion flow // Check if an import is being written if let Some(value) = completer.import_completions() { @@ -309,9 +309,10 @@ where Some(completions) } Located::Expression { - expression: TypedExpr::RecordAccess { record, .. }, + expression: TypedExpr::RecordAccess { record, type_, .. }, .. } => { + completer.expected_type = Some(type_.clone()); let mut completions = vec![]; completions.append(&mut completer.completion_values()); completions.append(&mut completer.completion_field_accessors(record.type_())); @@ -332,10 +333,14 @@ where ); Some(completions) } - Located::Statement(_) | Located::Expression { .. } => { + Located::Expression { expression, .. } => { + completer.expected_type = Some(expression.type_()); Some(completer.completion_values()) } Located::ModuleFunction(_) => Some(completer.completion_types()), + + Located::Statement(_) => Some(completer.completion_values()), + Located::FunctionBody(_) => Some(completer.completion_values()), Located::ModuleTypeAlias(_) diff --git a/compiler-core/src/language_server/tests/completion.rs b/compiler-core/src/language_server/tests/completion.rs index 8899f415e66..a76da9a350f 100644 --- a/compiler-core/src/language_server/tests/completion.rs +++ b/compiler-core/src/language_server/tests/completion.rs @@ -2252,3 +2252,45 @@ const x = "io." ); assert_eq!(completions, vec![],); } + +#[test] +fn prefer_values_matching_expected_type() { + let code = " +pub fn main() -> Bool { + let wibble = 123 + let wubble = True + let Wobble = 1.5 + w +} +"; + + assert_completion!(TestProject::for_source(code), Position::new(5, 3)); +} + +#[test] +fn prefer_function_which_returns_expected_type() { + let code = " +pub fn main() -> Int { + a +} + +fn add(a, b) { a + b } +fn sub(a, b) { a - b } +fn addf(a, b) { a +. b } +"; + + assert_completion!(TestProject::for_source(code), Position::new(2, 3)); +} + +#[test] +fn prefer_function_which_returns_expected_generic_type() { + let code = " +pub fn main() -> Result(Int, Nil) { + let result = Ok(12) + let result2 = Error(True) + r +} +"; + + assert_completion!(TestProject::for_source(code), Position::new(4, 3)); +} diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__argument_shadowing.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__argument_shadowing.snap index 30b26d7c46c..402e5445998 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__argument_shadowing.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__argument_shadowing.snap @@ -33,7 +33,7 @@ True main kind: Function detail: fn(Int) -> fn(Float) -> a - sort: 2_main + sort: 02_main desc: app edits: [3:0-3:0]: "main" 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 index 1b26429fd41..573a9c4a9db 100644 --- 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 @@ -17,8 +17,8 @@ fn fun() { wibble kind: Field detail: Int - sort: 1_wibble + sort: 01_wibble wobble kind: Field detail: Int - sort: 1_wobble + sort: 01_wobble diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access.snap index 73535271be9..41143db440f 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access.snap @@ -17,8 +17,8 @@ fn fun() { wibble kind: Field detail: Int - sort: 1_wibble + sort: 01_wibble wobble kind: Field detail: Int - sort: 1_wobble + sort: 01_wobble diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access_known_variant.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access_known_variant.snap index 0f3a236921b..3e9825f3437 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access_known_variant.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access_known_variant.snap @@ -19,16 +19,16 @@ fn fun(some_wibble: Wibble) { a kind: Field detail: Int - sort: 1_a + sort: 01_a b kind: Field detail: Int - sort: 1_b + sort: 01_b c kind: Field detail: Int - sort: 1_c + sort: 01_c d kind: Field detail: Int - sort: 1_d + sort: 01_d diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access_unknown_variant.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access_unknown_variant.snap index 139d9a9967b..5db054d3287 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access_unknown_variant.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_record_access_unknown_variant.snap @@ -16,4 +16,4 @@ fn fun(some_wibble: Wibble) { a kind: Field detail: Int - sort: 1_a + sort: 01_a diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_anonymous_function.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_anonymous_function.snap index 9ff78e59179..93b989708f7 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_anonymous_function.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_anonymous_function.snap @@ -32,14 +32,14 @@ True main kind: Function detail: fn() -> Int - sort: 2_main + sort: 02_main desc: app edits: [2:34-2:34]: "main" wibble kind: Variable detail: Int - sort: 2_wibble + sort: 02_wibble desc: app docs: "A locally defined variable." edits: diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_ignore_anonymous_function_args_nested.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_ignore_anonymous_function_args_nested.snap index 663d337ab82..0060bfd7397 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_ignore_anonymous_function_args_nested.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_ignore_anonymous_function_args_nested.snap @@ -36,7 +36,7 @@ True add_two kind: Variable detail: fn(Int) -> Int - sort: 2_add_two + sort: 02_add_two desc: app docs: "A locally defined variable." edits: @@ -44,14 +44,14 @@ add_two main kind: Function detail: fn() -> Int - sort: 2_main + sort: 02_main desc: app edits: [5:4-5:4]: "main" wabble kind: Variable detail: Int - sort: 2_wabble + sort: 02_wabble desc: app docs: "A locally defined variable." edits: @@ -59,7 +59,7 @@ wabble wibble kind: Variable detail: Int - sort: 2_wibble + sort: 02_wibble desc: app docs: "A locally defined variable." edits: diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_ignore_anonymous_function_returned.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_ignore_anonymous_function_returned.snap index 16ed6fdadcb..b93ec2f98c5 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_ignore_anonymous_function_returned.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_ignore_anonymous_function_returned.snap @@ -35,7 +35,7 @@ True add_two kind: Variable detail: fn(Int) -> Int - sort: 2_add_two + sort: 02_add_two desc: app docs: "A locally defined variable." edits: @@ -50,7 +50,7 @@ main wabble kind: Variable detail: Int - sort: 2_wabble + sort: 02_wabble desc: app docs: "A locally defined variable." edits: @@ -58,7 +58,7 @@ wabble wibble kind: Variable detail: Int - sort: 2_wibble + sort: 02_wibble desc: app docs: "A locally defined variable." edits: diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_inside_nested_exprs.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_inside_nested_exprs.snap index 67b0cdd6504..74aa7fc7650 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_inside_nested_exprs.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_inside_nested_exprs.snap @@ -27,7 +27,7 @@ Error False kind: EnumMember detail: gleam - sort: 4_False + sort: 04_False Nil kind: EnumMember detail: gleam @@ -39,11 +39,11 @@ Ok True kind: EnumMember detail: gleam - sort: 4_True + sort: 04_True foo kind: Variable detail: Bool - sort: 2_foo + sort: 02_foo desc: app docs: "A locally defined variable." edits: diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_nested_anonymous_function.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_nested_anonymous_function.snap index c1ff8d8421e..37e28dce3fb 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_nested_anonymous_function.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_nested_anonymous_function.snap @@ -36,14 +36,14 @@ True main kind: Function detail: fn() -> Int - sort: 2_main + sort: 02_main desc: app edits: [4:36-4:36]: "main" wabble kind: Variable detail: Int - sort: 2_wabble + sort: 02_wabble desc: app docs: "A locally defined variable." edits: @@ -51,7 +51,7 @@ wabble wibble kind: Variable detail: Int - sort: 2_wibble + sort: 02_wibble desc: app docs: "A locally defined variable." edits: @@ -59,7 +59,7 @@ wibble wobble kind: Variable detail: Int - sort: 2_wobble + sort: 02_wobble desc: app docs: "A locally defined variable." edits: diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_pipe.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_pipe.snap index be6479242aa..952dcfaf6da 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_pipe.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__local_variable_pipe.snap @@ -33,7 +33,7 @@ True add_one kind: Variable detail: fn(Int) -> Int - sort: 2_add_one + sort: 02_add_one desc: app docs: "A locally defined variable." edits: diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__no_variable_completions_after_case_clause_scope.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__no_variable_completions_after_case_clause_scope.snap index 6d54c38171b..aed01cfb059 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__no_variable_completions_after_case_clause_scope.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__no_variable_completions_after_case_clause_scope.snap @@ -23,7 +23,7 @@ False Nil kind: EnumMember detail: gleam - sort: 4_Nil + sort: 04_Nil Ok kind: Constructor detail: gleam @@ -42,7 +42,7 @@ main something_else kind: Variable detail: a - sort: 2_something_else + sort: 02_something_else desc: app docs: "A locally defined variable." edits: diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__no_variable_completions_before_case_clause.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__no_variable_completions_before_case_clause.snap index b643c293b24..b34019d93a0 100644 --- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__no_variable_completions_before_case_clause.snap +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__no_variable_completions_before_case_clause.snap @@ -23,7 +23,7 @@ False Nil kind: EnumMember detail: gleam - sort: 4_Nil + sort: 04_Nil Ok kind: Constructor detail: gleam @@ -42,7 +42,7 @@ main something kind: Variable detail: a - sort: 2_something + sort: 02_something desc: app docs: "A locally defined variable." edits: diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__prefer_function_which_returns_expected_generic_type.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__prefer_function_which_returns_expected_generic_type.snap new file mode 100644 index 00000000000..04ffe3c754c --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__prefer_function_which_returns_expected_generic_type.snap @@ -0,0 +1,55 @@ +--- +source: compiler-core/src/language_server/tests/completion.rs +expression: "\npub fn main() -> Result(Int, Nil) {\n let result = Ok(12)\n let result2 = Error(True)\n r\n}\n" +--- +pub fn main() -> Result(Int, Nil) { + let result = Ok(12) + let result2 = Error(True) + r| +} + + +----- Completion content ----- +Error + kind: Constructor + detail: gleam + sort: 04_Error +False + kind: EnumMember + detail: gleam + sort: 4_False +Nil + kind: EnumMember + detail: gleam + sort: 4_Nil +Ok + kind: Constructor + detail: gleam + sort: 04_Ok +True + kind: EnumMember + detail: gleam + sort: 4_True +main + kind: Function + detail: fn() -> Result(Int, Nil) + sort: 02_main + desc: app + edits: + [4:2-4:2]: "main" +result + kind: Variable + detail: Result(Int, a) + sort: 02_result + desc: app + docs: "A locally defined variable." + edits: + [4:2-4:2]: "result" +result2 + kind: Variable + detail: Result(a, Bool) + sort: 2_result2 + desc: app + docs: "A locally defined variable." + edits: + [4:2-4:2]: "result2" diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__prefer_function_which_returns_expected_type.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__prefer_function_which_returns_expected_type.snap new file mode 100644 index 00000000000..6a4bea1c1e2 --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__prefer_function_which_returns_expected_type.snap @@ -0,0 +1,62 @@ +--- +source: compiler-core/src/language_server/tests/completion.rs +expression: "\npub fn main() -> Int {\n a\n}\n\nfn add(a, b) { a + b }\nfn sub(a, b) { a - b }\nfn addf(a, b) { a +. b }\n" +--- +pub fn main() -> Int { + a| +} + +fn add(a, b) { a + b } +fn sub(a, b) { a - b } +fn addf(a, b) { a +. b } + + +----- Completion content ----- +Error + kind: Constructor + detail: gleam + sort: 4_Error +False + kind: EnumMember + detail: gleam + sort: 4_False +Nil + kind: EnumMember + detail: gleam + sort: 4_Nil +Ok + kind: Constructor + detail: gleam + sort: 4_Ok +True + kind: EnumMember + detail: gleam + sort: 4_True +add + kind: Function + detail: fn(Int, Int) -> Int + sort: 02_add + desc: app + edits: + [2:2-2:2]: "add" +addf + kind: Function + detail: fn(Float, Float) -> Float + sort: 2_addf + desc: app + edits: + [2:2-2:2]: "addf" +main + kind: Function + detail: fn() -> Int + sort: 02_main + desc: app + edits: + [2:2-2:2]: "main" +sub + kind: Function + detail: fn(Int, Int) -> Int + sort: 02_sub + desc: app + edits: + [2:2-2:2]: "sub" diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__prefer_values_matching_expected_type.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__prefer_values_matching_expected_type.snap new file mode 100644 index 00000000000..60c0f4e0833 --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__prefer_values_matching_expected_type.snap @@ -0,0 +1,56 @@ +--- +source: compiler-core/src/language_server/tests/completion.rs +expression: "\npub fn main() -> Bool {\n let wibble = 123\n let wubble = True\n let Wobble = 1.5\n w\n}\n" +--- +pub fn main() -> Bool { + let wibble = 123 + let wubble = True + let Wobble = 1.5 + w| +} + + +----- Completion content ----- +Error + kind: Constructor + detail: gleam + sort: 4_Error +False + kind: EnumMember + detail: gleam + sort: 04_False +Nil + kind: EnumMember + detail: gleam + sort: 4_Nil +Ok + kind: Constructor + detail: gleam + sort: 4_Ok +True + kind: EnumMember + detail: gleam + sort: 04_True +main + kind: Function + detail: fn() -> Bool + sort: 02_main + desc: app + edits: + [5:2-5:2]: "main" +wibble + kind: Variable + detail: Int + sort: 2_wibble + desc: app + docs: "A locally defined variable." + edits: + [5:2-5:2]: "wibble" +wubble + kind: Variable + detail: Bool + sort: 02_wubble + desc: app + docs: "A locally defined variable." + edits: + [5:2-5:2]: "wubble" diff --git a/compiler-core/src/type_.rs b/compiler-core/src/type_.rs index 82c123d4cdf..276b22ea2ca 100644 --- a/compiler-core/src/type_.rs +++ b/compiler-core/src/type_.rs @@ -565,7 +565,11 @@ impl Type { && package == other_package && module == other_module && name == other_name - && arguments == other_arguments + && arguments.len() == other_arguments.len() + && arguments + .iter() + .zip(other_arguments) + .all(|(one, other)| one.same_as(other)) } (Type::Fn { .. }, Type::Named { .. } | Type::Tuple { .. }) => false, @@ -1210,7 +1214,8 @@ impl TypeVar { pub fn is_unbound(&self) -> bool { match self { Self::Unbound { .. } => true, - Self::Link { .. } | Self::Generic { .. } => false, + Self::Link { type_ } => type_.is_unbound(), + Self::Generic { .. } => false, } }