diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index abae3cb36802..856f53210f29 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -33,7 +33,7 @@ use crate::{ CompletionContext, CompletionItem, CompletionItemKind, context::{ DotAccess, ItemListKind, NameContext, NameKind, NameRefContext, NameRefKind, - PathCompletionCtx, PathKind, PatternContext, TypeLocation, Visible, + PathCompletionCtx, PathKind, PatternContext, TypeAscriptionTarget, TypeLocation, Visible, }, item::Builder, render::{ @@ -111,11 +111,22 @@ impl Completions { } } - pub(crate) fn add_type_keywords(&mut self, ctx: &CompletionContext<'_>) { - self.add_keyword_snippet(ctx, "fn", "fn($1)"); - self.add_keyword_snippet(ctx, "dyn", "dyn $0"); - self.add_keyword_snippet(ctx, "impl", "impl $0"); - self.add_keyword_snippet(ctx, "for", "for<$1>"); + pub(crate) fn add_type_keywords( + &mut self, + ctx: &CompletionContext<'_>, + required_thin_arrow: bool, + ) { + let mut add_keyword = |kw, snippet| { + if required_thin_arrow { + self.add_keyword_snippet(ctx, kw, snippet); + } else { + self.add_keyword_snippet(ctx, kw, &snippet[3..]); + } + }; + add_keyword("fn", "-> fn($1)"); + add_keyword("dyn", "-> dyn $0"); + add_keyword("impl", "-> impl $0"); + add_keyword("for", "-> for<$1>"); } pub(crate) fn add_super_keyword( @@ -746,6 +757,12 @@ pub(super) fn complete_name_ref( field::complete_field_list_tuple_variant(acc, ctx, path_ctx); } TypeLocation::TypeAscription(ascription) => { + if let TypeAscriptionTarget::RetType { item: Some(item), .. } = + ascription + && path_ctx.required_thin_arrow() + { + keyword::complete_for_and_where(acc, ctx, &item.clone().into()); + } r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription); } TypeLocation::GenericArg { .. } diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index 3465b73321e9..48ee967bafa2 100644 --- a/crates/ide-completion/src/completions/type.rs +++ b/crates/ide-completion/src/completions/type.rs @@ -204,8 +204,9 @@ pub(crate) fn complete_type_path( _ => {} }; + // FIXME: "crate::" -> "-> crate::" when required_thin_arrow acc.add_nameref_keywords_with_colon(ctx); - acc.add_type_keywords(ctx); + acc.add_type_keywords(ctx, path_ctx.required_thin_arrow()); ctx.process_all_names(&mut |name, def, doc_aliases| { if scope_def_applicable(def) { acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases); @@ -228,14 +229,14 @@ pub(crate) fn complete_ascribed_type( TypeAscriptionTarget::Let(pat) | TypeAscriptionTarget::FnParam(pat) => { ctx.sema.type_of_pat(pat.as_ref()?) } - TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType(exp) => { + TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType { body: exp, .. } => { ctx.sema.type_of_expr(exp.as_ref()?) } }? .adjusted(); if !ty.is_unknown() { let ty_string = ty.display_source_code(ctx.db, ctx.module.into(), true).ok()?; - acc.add(render_type_inference(ty_string, ctx)); + acc.add(render_type_inference(ty_string, ctx, path_ctx)); } None } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 2f166b718451..437049a2548f 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -101,6 +101,30 @@ impl PathCompletionCtx<'_> { } ) } + + pub(crate) fn required_thin_arrow(&self) -> bool { + let PathKind::Type { + location: + TypeLocation::TypeAscription(TypeAscriptionTarget::RetType { + item: Some(ref fn_item), + .. + }), + } = self.kind + else { + return false; + }; + let Some(ret_type) = fn_item.ret_type() else { return true }; + if ret_type.thin_arrow_token().is_some() { + return false; + } + match ret_type.ty() { + Some(ast::Type::PathType(path_ty)) => { + path_ty.path().is_some_and(|path| path.as_single_name_ref().is_some()) + } + Some(_) => false, + None => true, + } + } } /// The kind of path we are completing right now. @@ -230,7 +254,7 @@ impl TypeLocation { pub(crate) enum TypeAscriptionTarget { Let(Option), FnParam(Option), - RetType(Option), + RetType { body: Option, item: Option }, Const(Option), } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index d6d3978385d9..7b355d926af4 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -1200,15 +1200,14 @@ fn classify_name_ref<'db>( let original = ast::Const::cast(name.syntax().parent()?)?; TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body())) }, - ast::RetType(it) => { - it.thin_arrow_token()?; + ast::RetType(_) => { let parent = match ast::Fn::cast(parent.parent()?) { Some(it) => it.param_list(), None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(), }; let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?; - TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! { + let body = match_ast! { match parent { ast::ClosureExpr(it) => { it.body() @@ -1218,7 +1217,9 @@ fn classify_name_ref<'db>( }, _ => return None, } - })) + }; + let item = ast::Fn::cast(parent); + TypeLocation::TypeAscription(TypeAscriptionTarget::RetType { body, item }) }, ast::Param(it) => { it.colon_token()?; diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 303c71230d60..5266651f0182 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -478,7 +478,7 @@ impl CompletionItem { /// A helper to make `CompletionItem`s. #[must_use] -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) struct Builder { source_range: TextRange, imports_to_add: SmallVec<[LocatedImport; 1]>, diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index c0f09e1d950d..dd3c7f3d02d6 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -221,13 +221,17 @@ pub(crate) fn render_tuple_field( pub(crate) fn render_type_inference( ty_string: String, ctx: &CompletionContext<'_>, + path_ctx: &PathCompletionCtx<'_>, ) -> CompletionItem { let mut builder = CompletionItem::new( CompletionItemKind::InferredType, ctx.source_range(), - ty_string, + &ty_string, ctx.edition, ); + if path_ctx.required_thin_arrow() { + builder.insert_text(format!("-> {ty_string}")); + } builder.set_relevance(CompletionRelevance { type_match: Some(CompletionRelevanceTypeMatch::Exact), exact_name_match: true, @@ -423,11 +427,13 @@ fn render_resolution_path( let db = completion.db; let config = completion.config; let requires_import = import_to_add.is_some(); + let arrow = if path_ctx.required_thin_arrow() { "-> " } else { "" }; - let name = local_name.display_no_db(ctx.completion.edition).to_smolstr(); + let name = local_name.display(db, completion.edition).to_smolstr(); + let prefix = format_args!("{arrow}{name}"); let mut item = render_resolution_simple_(ctx, &local_name, import_to_add, resolution); - if local_name.needs_escape(completion.edition) { - item.insert_text(local_name.display_no_db(completion.edition).to_smolstr()); + if local_name.needs_escape(completion.edition) || !arrow.is_empty() { + item.insert_text(prefix.to_smolstr()); } // Add `<>` for generic types let type_path_no_ty_args = matches!( @@ -448,7 +454,7 @@ fn render_resolution_path( item.lookup_by(name.clone()) .label(SmolStr::from_iter([&name, "<…>"])) .trigger_call_info() - .insert_snippet(cap, format!("{}<$0>", local_name.display(db, completion.edition))); + .insert_snippet(cap, format!("{prefix}<$0>")); } } diff --git a/crates/ide-completion/src/tests/item.rs b/crates/ide-completion/src/tests/item.rs index 61a9da8c278a..0a5dca9eec6e 100644 --- a/crates/ide-completion/src/tests/item.rs +++ b/crates/ide-completion/src/tests/item.rs @@ -116,8 +116,23 @@ fn completes_where() { check_with_base_items( r"fn func() $0", expect![[r#" - kw where - "#]], + en Enum Enum + ma makro!(…) macro_rules! makro + md module + st Record Record + st Tuple Tuple + st Unit Unit + tt Trait + un Union Union + bt u32 u32 + kw crate:: + kw dyn + kw fn + kw for + kw impl + kw self:: + kw where + "#]], ); check_with_base_items( r"enum Enum $0", @@ -243,6 +258,19 @@ impl Copy for S where $0 ); } +#[test] +fn fn_item_where_kw() { + check_edit( + "where", + r#" +fn foo() $0 +"#, + r#" +fn foo() where $0 +"#, + ); +} + #[test] fn test_is_not_considered_macro() { check_with_base_items( diff --git a/crates/ide-completion/src/tests/type_pos.rs b/crates/ide-completion/src/tests/type_pos.rs index 3bbba18c2b9f..f8b1f5a79b27 100644 --- a/crates/ide-completion/src/tests/type_pos.rs +++ b/crates/ide-completion/src/tests/type_pos.rs @@ -1,7 +1,7 @@ //! Completion tests for type position. use expect_test::expect; -use crate::tests::{check, check_with_base_items}; +use crate::tests::{check, check_edit, check_with_base_items}; #[test] fn record_field_ty() { @@ -93,6 +93,193 @@ fn x<'lt, T, const C: usize>() -> $0 ); } +#[test] +fn fn_return_type_missing_thin_arrow() { + check_with_base_items( + r#" +fn x() u$0 +"#, + expect![[r#" + en Enum Enum + ma makro!(…) macro_rules! makro + md module + st Record Record + st Tuple Tuple + st Unit Unit + tt Trait + un Union Union + bt u32 u32 + kw crate:: + kw dyn + kw fn + kw for + kw impl + kw self:: + kw where + "#]], + ); + + check_with_base_items( + r#" +fn x() $0 +"#, + expect![[r#" + en Enum Enum + ma makro!(…) macro_rules! makro + md module + st Record Record + st Tuple Tuple + st Unit Unit + tt Trait + un Union Union + bt u32 u32 + kw crate:: + kw dyn + kw fn + kw for + kw impl + kw self:: + kw where + "#]], + ); +} + +#[test] +fn fn_return_type_missing_thin_arrow_path_completion() { + check_edit( + "u32", + r#" +fn foo() u$0 +"#, + r#" +fn foo() -> u32 +"#, + ); + + check_edit( + "u32", + r#" +fn foo() $0 +"#, + r#" +fn foo() -> u32 +"#, + ); + + check_edit( + "Num", + r#" +type Num = u32; +fn foo() $0 +"#, + r#" +type Num = u32; +fn foo() -> Num +"#, + ); + + check_edit( + "impl", + r#" +fn foo() $0 +"#, + r#" +fn foo() -> impl $0 +"#, + ); + + check_edit( + "foo", + r#" +mod foo { pub type Num = u32; } +fn foo() $0 +"#, + r#" +mod foo { pub type Num = u32; } +fn foo() -> foo +"#, + ); +} + +#[test] +fn fn_return_type_missing_thin_arrow_path_completion_with_generic_args() { + check_edit( + "Foo", + r#" +struct Foo(T); +fn foo() F$0 +"#, + r#" +struct Foo(T); +fn foo() -> Foo<$0> +"#, + ); + + check_edit( + "Foo", + r#" +struct Foo(T); +fn foo() $0 +"#, + r#" +struct Foo(T); +fn foo() -> Foo<$0> +"#, + ); + + check_edit( + "Foo", + r#" +type Foo = T; +fn foo() $0 +"#, + r#" +type Foo = T; +fn foo() -> Foo<$0> +"#, + ); +} + +#[test] +fn fn_return_type_missing_thin_arrow_infer_ref_type() { + check_with_base_items( + r#" +fn x() u$0 {&2u32} +"#, + expect![[r#" + en Enum Enum + ma makro!(…) macro_rules! makro + md module + st Record Record + st Tuple Tuple + st Unit Unit + tt Trait + un Union Union + bt u32 u32 + it &u32 + kw crate:: + kw dyn + kw fn + kw for + kw impl + kw self:: + kw where + "#]], + ); + + check_edit( + "&u32", + r#" +struct Foo(T); +fn x() u$0 {&2u32} +"#, + r#" +struct Foo(T); +fn x() -> &u32 {&2u32} +"#, + ); +} + #[test] fn fn_return_type_after_reference() { check_with_base_items( diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index ed55ac5bf04b..a587b85ff667 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -1240,12 +1240,6 @@ sdasdasdasdasd #[test] fn parenthesis_noop_in_item_position_with_macro() { type_char_noop('(', r#"$0println!();"#); - type_char_noop( - '(', - r#" -fn main() $0println!("hello"); -}"#, - ); } #[test] diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs index bf8430294110..237a39d465e5 100644 --- a/crates/parser/src/grammar.rs +++ b/crates/parser/src/grammar.rs @@ -303,6 +303,18 @@ fn opt_ret_type(p: &mut Parser<'_>) -> bool { } } +fn opt_no_arrow_ret_type(p: &mut Parser<'_>) -> bool { + if p.at_ts(PATH_NAME_REF_KINDS) { + let m = p.start(); + p.error("missing thin-arrow `->`"); + types::type_no_bounds(p); + m.complete(p, RET_TYPE); + true + } else { + false + } +} + fn name_r(p: &mut Parser<'_>, recovery: TokenSet) { if p.at(IDENT) { let m = p.start(); diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs index c609f9383ee0..c0acdde2a724 100644 --- a/crates/parser/src/grammar/items.rs +++ b/crates/parser/src/grammar/items.rs @@ -422,7 +422,12 @@ fn fn_(p: &mut Parser<'_>, m: Marker) { // test function_ret_type // fn foo() {} // fn bar() -> () {} - opt_ret_type(p); + if !opt_ret_type(p) { + // test_err function_ret_type_missing_arrow + // fn foo() usize {} + // fn bar() super::Foo {} + opt_no_arrow_ret_type(p); + } // test_err fn_ret_recovery // fn foo() -> A>]) { let x = 1; } diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index 7f5ff0ec0735..717602773d87 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -783,6 +783,10 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/fn_ret_recovery.rs"); } #[test] + fn function_ret_type_missing_arrow() { + run_and_expect_errors("test_data/parser/inline/err/function_ret_type_missing_arrow.rs"); + } + #[test] fn gen_fn() { run_and_expect_errors_with_edition( "test_data/parser/inline/err/gen_fn.rs", diff --git a/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rast b/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rast new file mode 100644 index 000000000000..c0bca6ed1c31 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rast @@ -0,0 +1,50 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "usize" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "bar" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + PATH_TYPE + PATH + PATH + PATH_SEGMENT + NAME_REF + SUPER_KW "super" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "Foo" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" +error 9: missing thin-arrow `->` +error 27: missing thin-arrow `->` diff --git a/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rs b/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rs new file mode 100644 index 000000000000..f48e539df50d --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rs @@ -0,0 +1,2 @@ +fn foo() usize {} +fn bar() super::Foo {}