Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-completion/src/completions.rs43
-rw-r--r--crates/ide-completion/src/completions/type.rs8
-rw-r--r--crates/ide-completion/src/context.rs24
-rw-r--r--crates/ide-completion/src/context/analysis.rs9
-rw-r--r--crates/ide-completion/src/item.rs36
-rw-r--r--crates/ide-completion/src/render.rs54
-rw-r--r--crates/ide-completion/src/tests/item.rs32
-rw-r--r--crates/ide-completion/src/tests/type_pos.rs226
-rw-r--r--crates/ide/src/typing.rs6
-rw-r--r--crates/parser/src/grammar.rs12
-rw-r--r--crates/parser/src/grammar/items.rs7
-rw-r--r--crates/parser/test_data/generated/runner.rs4
-rw-r--r--crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rast50
-rw-r--r--crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rs2
14 files changed, 467 insertions, 46 deletions
diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs
index 1fb1fd4e57..4a94383ff4 100644
--- a/crates/ide-completion/src/completions.rs
+++ b/crates/ide-completion/src/completions.rs
@@ -34,7 +34,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::{
@@ -45,7 +45,7 @@ use crate::{
macro_::render_macro,
pattern::{render_struct_pat, render_variant_pat},
render_expr, render_field, render_path_resolution, render_pattern_resolution,
- render_tuple_field,
+ render_tuple_field, render_type_keyword_snippet,
type_alias::{render_type_alias, render_type_alias_with_eq},
union_literal::render_union_literal,
},
@@ -104,6 +104,21 @@ impl Completions {
}
}
+ pub(crate) fn add_nameref_keywords_with_type_like(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx<'_>,
+ ) {
+ let mut add_keyword = |kw| {
+ render_type_keyword_snippet(ctx, path_ctx, kw, kw).add_to(self, ctx.db);
+ };
+ ["self::", "crate::"].into_iter().for_each(&mut add_keyword);
+
+ if ctx.depth_from_crate_root > 0 {
+ add_keyword("super::");
+ }
+ }
+
pub(crate) fn add_nameref_keywords(&mut self, ctx: &CompletionContext<'_>) {
["self", "crate"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
@@ -112,11 +127,19 @@ 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<'_>,
+ path_ctx: &PathCompletionCtx<'_>,
+ ) {
+ let mut add_keyword = |kw, snippet| {
+ render_type_keyword_snippet(ctx, path_ctx, kw, snippet).add_to(self, ctx.db);
+ };
+
+ 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(
@@ -747,6 +770,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().is_some()
+ {
+ 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 8ff9c3258e..e2125a9678 100644
--- a/crates/ide-completion/src/completions/type.rs
+++ b/crates/ide-completion/src/completions/type.rs
@@ -206,8 +206,8 @@ pub(crate) fn complete_type_path(
_ => {}
};
- acc.add_nameref_keywords_with_colon(ctx);
- acc.add_type_keywords(ctx);
+ acc.add_nameref_keywords_with_type_like(ctx, path_ctx);
+ acc.add_type_keywords(ctx, path_ctx);
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);
@@ -230,14 +230,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 a91f123176..ae3f717607 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -102,6 +102,28 @@ impl PathCompletionCtx<'_> {
}
)
}
+
+ pub(crate) fn required_thin_arrow(&self) -> Option<(&'static str, TextSize)> {
+ let PathKind::Type {
+ location:
+ TypeLocation::TypeAscription(TypeAscriptionTarget::RetType {
+ item: Some(ref fn_item),
+ ..
+ }),
+ } = self.kind
+ else {
+ return None;
+ };
+ if fn_item.ret_type().is_some_and(|it| it.thin_arrow_token().is_some()) {
+ return None;
+ }
+ let ret_type = fn_item.ret_type().and_then(|it| it.ty());
+ match (ret_type, fn_item.param_list()) {
+ (Some(ty), _) => Some(("-> ", ty.syntax().text_range().start())),
+ (None, Some(param)) => Some((" ->", param.syntax().text_range().end())),
+ (None, None) => None,
+ }
+ }
}
/// The kind of path we are completing right now.
@@ -231,7 +253,7 @@ impl TypeLocation {
pub(crate) enum TypeAscriptionTarget {
Let(Option<ast::Pat>),
FnParam(Option<ast::Pat>),
- RetType(Option<ast::Expr>),
+ RetType { body: Option<ast::Expr>, item: Option<ast::Fn> },
Const(Option<ast::Expr>),
}
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 294e70dd56..d8f160c100 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -1278,15 +1278,14 @@ fn classify_name_ref<'db>(
let original = ast::Static::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()
@@ -1296,7 +1295,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 1a9139d855..e6dd1d37d9 100644
--- a/crates/ide-completion/src/item.rs
+++ b/crates/ide-completion/src/item.rs
@@ -450,6 +450,7 @@ impl CompletionItem {
ref_match: None,
imports_to_add: Default::default(),
doc_aliases: vec![],
+ adds_text: None,
edition,
}
}
@@ -480,12 +481,13 @@ 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]>,
trait_name: Option<SmolStr>,
doc_aliases: Vec<SmolStr>,
+ adds_text: Option<SmolStr>,
label: SmolStr,
insert_text: Option<String>,
is_snippet: bool,
@@ -526,9 +528,16 @@ impl Builder {
let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
let mut detail_left = None;
+ let mut to_detail_left = |args: fmt::Arguments<'_>| {
+ let detail_left = detail_left.get_or_insert_with(String::new);
+ if !detail_left.is_empty() {
+ detail_left.push(' ');
+ }
+ format_to!(detail_left, "{args}")
+ };
if !self.doc_aliases.is_empty() {
let doc_aliases = self.doc_aliases.iter().join(", ");
- detail_left = Some(format!("(alias {doc_aliases})"));
+ to_detail_left(format_args!("(alias {doc_aliases})"));
let lookup_doc_aliases = self
.doc_aliases
.iter()
@@ -548,22 +557,17 @@ impl Builder {
lookup = format_smolstr!("{lookup}{lookup_doc_aliases}");
}
}
+ if let Some(adds_text) = self.adds_text {
+ to_detail_left(format_args!("(adds {})", adds_text.trim()));
+ }
if let [import_edit] = &*self.imports_to_add {
// snippets can have multiple imports, but normal completions only have up to one
- let detail_left = detail_left.get_or_insert_with(String::new);
- format_to!(
- detail_left,
- "{}(use {})",
- if detail_left.is_empty() { "" } else { " " },
+ to_detail_left(format_args!(
+ "(use {})",
import_edit.import_path.display(db, self.edition)
- );
+ ));
} else if let Some(trait_name) = self.trait_name {
- let detail_left = detail_left.get_or_insert_with(String::new);
- format_to!(
- detail_left,
- "{}(as {trait_name})",
- if detail_left.is_empty() { "" } else { " " },
- );
+ to_detail_left(format_args!("(as {trait_name})"));
}
let text_edit = match self.text_edit {
@@ -613,6 +617,10 @@ impl Builder {
self.doc_aliases = doc_aliases;
self
}
+ pub(crate) fn adds_text(&mut self, adds_text: SmolStr) -> &mut Builder {
+ self.adds_text = Some(adds_text);
+ self
+ }
pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
self.insert_text = Some(insert_text.into());
self
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 4751ee36ec..f37b73a28a 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -220,13 +220,15 @@ 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,
);
+ adds_ret_type_arrow(ctx, path_ctx, &mut builder, ty_string);
builder.set_relevance(CompletionRelevance {
type_match: Some(CompletionRelevanceTypeMatch::Exact),
exact_name_match: true,
@@ -425,11 +427,10 @@ fn render_resolution_path(
let config = completion.config;
let requires_import = import_to_add.is_some();
- let name = local_name.display_no_db(ctx.completion.edition).to_smolstr();
+ let name = local_name.display(db, completion.edition).to_smolstr();
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());
- }
+ let mut insert_text = name.clone();
+
// Add `<>` for generic types
let type_path_no_ty_args = matches!(
path_ctx,
@@ -446,12 +447,14 @@ fn render_resolution_path(
if has_non_default_type_params {
cov_mark::hit!(inserts_angle_brackets_for_generics);
+ insert_text = format_smolstr!("{insert_text}<$0>");
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, ""); // set is snippet
}
}
+ adds_ret_type_arrow(completion, path_ctx, &mut item, insert_text.into());
let mut set_item_relevance = |ty: Type<'_>| {
if !ty.is_unknown() {
@@ -577,6 +580,45 @@ fn scope_def_is_deprecated(ctx: &RenderContext<'_>, resolution: ScopeDef) -> boo
}
}
+pub(crate) fn render_type_keyword_snippet(
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx<'_>,
+ label: &str,
+ snippet: &str,
+) -> Builder {
+ let source_range = ctx.source_range();
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Keyword, source_range, label, ctx.edition);
+
+ let cap = ctx.config.snippet_cap;
+ if let Some(cap) = cap {
+ item.insert_snippet(cap, snippet);
+ }
+
+ let insert_text = if cap.is_some() { snippet } else { label }.to_owned();
+ adds_ret_type_arrow(ctx, path_ctx, &mut item, insert_text);
+
+ item
+}
+
+fn adds_ret_type_arrow(
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx<'_>,
+ item: &mut Builder,
+ insert_text: String,
+) {
+ if let Some((arrow, at)) = path_ctx.required_thin_arrow() {
+ let mut edit = TextEdit::builder();
+
+ edit.insert(at, arrow.to_owned());
+ edit.replace(ctx.source_range(), insert_text);
+
+ item.text_edit(edit.finish()).adds_text(SmolStr::new_static(arrow));
+ } else {
+ item.insert_text(insert_text);
+ }
+}
+
// FIXME: This checks types without possible coercions which some completions might want to do
fn match_types(
ctx: &CompletionContext<'_>,
diff --git a/crates/ide-completion/src/tests/item.rs b/crates/ide-completion/src/tests/item.rs
index 61a9da8c27..2f032c3f4c 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 (adds ->) Enum
+ ma makro!(…) macro_rules! makro
+ md module (adds ->)
+ st Record (adds ->) Record
+ st Tuple (adds ->) Tuple
+ st Unit (adds ->) Unit
+ tt Trait (adds ->)
+ un Union (adds ->) Union
+ bt u32 (adds ->) u32
+ kw crate:: (adds ->)
+ kw dyn (adds ->)
+ kw fn (adds ->)
+ kw for (adds ->)
+ kw impl (adds ->)
+ kw self:: (adds ->)
+ kw where
+ "#]],
);
check_with_base_items(
r"enum Enum $0",
@@ -244,6 +259,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(
r#"
diff --git a/crates/ide-completion/src/tests/type_pos.rs b/crates/ide-completion/src/tests/type_pos.rs
index 7c6b7370aa..7d4a7fe6b8 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() {
@@ -94,6 +94,230 @@ 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 (adds ->) Enum
+ ma makro!(…) macro_rules! makro
+ md module (adds ->)
+ st Record (adds ->) Record
+ st Tuple (adds ->) Tuple
+ st Unit (adds ->) Unit
+ tt Trait (adds ->)
+ un Union (adds ->) Union
+ bt u32 (adds ->) u32
+ kw crate:: (adds ->)
+ kw dyn (adds ->)
+ kw fn (adds ->)
+ kw for (adds ->)
+ kw impl (adds ->)
+ kw self:: (adds ->)
+ kw where
+ "#]],
+ );
+
+ check_with_base_items(
+ r#"
+fn x() $0
+"#,
+ expect![[r#"
+ en Enum (adds ->) Enum
+ ma makro!(…) macro_rules! makro
+ md module (adds ->)
+ st Record (adds ->) Record
+ st Tuple (adds ->) Tuple
+ st Unit (adds ->) Unit
+ tt Trait (adds ->)
+ un Union (adds ->) Union
+ bt u32 (adds ->) u32
+ kw crate:: (adds ->)
+ kw dyn (adds ->)
+ kw fn (adds ->)
+ kw for (adds ->)
+ kw impl (adds ->)
+ kw self:: (adds ->)
+ 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
+"#,
+ );
+
+ check_edit(
+ "crate::",
+ r#"
+mod foo { pub type Num = u32; }
+fn foo() $0
+"#,
+ r#"
+mod foo { pub type Num = u32; }
+fn foo() -> crate::
+"#,
+ );
+
+ check_edit(
+ "Num",
+ r#"
+mod foo { pub type Num = u32; }
+fn foo() foo::$0
+"#,
+ r#"
+mod foo { pub type Num = u32; }
+fn foo() -> foo::Num
+"#,
+ );
+
+ // no spaces, test edit order
+ 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>(T);
+fn foo() F$0
+"#,
+ r#"
+struct Foo<T>(T);
+fn foo() -> Foo<$0>
+"#,
+ );
+
+ check_edit(
+ "Foo",
+ r#"
+struct Foo<T>(T);
+fn foo() $0
+"#,
+ r#"
+struct Foo<T>(T);
+fn foo() -> Foo<$0>
+"#,
+ );
+
+ check_edit(
+ "Foo",
+ r#"
+type Foo<T> = T;
+fn foo() $0
+"#,
+ r#"
+type Foo<T> = 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 (adds ->) Enum
+ ma makro!(…) macro_rules! makro
+ md module (adds ->)
+ st Record (adds ->) Record
+ st Tuple (adds ->) Tuple
+ st Unit (adds ->) Unit
+ tt Trait (adds ->)
+ un Union (adds ->) Union
+ bt u32 (adds ->) u32
+ it &u32 (adds ->)
+ kw crate:: (adds ->)
+ kw dyn (adds ->)
+ kw fn (adds ->)
+ kw for (adds ->)
+ kw impl (adds ->)
+ kw self:: (adds ->)
+ kw where
+ "#]],
+ );
+
+ check_edit(
+ "&u32",
+ r#"
+struct Foo<T>(T);
+fn x() u$0 {&2u32}
+"#,
+ r#"
+struct Foo<T>(T);
+fn x() -> &u32 {&2u32}
+"#,
+ );
+}
+
+#[test]
fn fn_return_type_after_reference() {
check_with_base_items(
r#"
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index ec620982ff..a49a85fe78 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -1239,12 +1239,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 e481bbe9bc..1ff8a56b58 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 c609f9383e..c0acdde2a7 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 4c001104fe..01fc172ed9 100644
--- a/crates/parser/test_data/generated/runner.rs
+++ b/crates/parser/test_data/generated/runner.rs
@@ -793,6 +793,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 0000000000..c0bca6ed1c
--- /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 0000000000..f48e539df5
--- /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 {}