From 03a6ab0b62b2fbaefbf39bee6605b3a1050b8083 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 14 Mar 2023 21:55:03 +0100 Subject: [PATCH 1/2] Add signature help for record struct patterns --- crates/hir/src/semantics.rs | 4 +- crates/hir/src/source_analyzer.rs | 7 ++- crates/ide-db/src/defs.rs | 9 ++- crates/ide/src/goto_type_definition.rs | 2 +- crates/ide/src/signature_help.rs | 80 +++++++++++++++++++++++--- 5 files changed, 88 insertions(+), 14 deletions(-) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index ab39542f9f..407ba6f658 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -411,7 +411,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.resolve_record_field(field) } - pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option { + pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<(Field, Type)> { self.imp.resolve_record_pat_field(field) } @@ -1201,7 +1201,7 @@ impl<'db> SemanticsImpl<'db> { self.analyze(field.syntax())?.resolve_record_field(self.db, field) } - fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option { + fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<(Field, Type)> { self.analyze(field.syntax())?.resolve_record_pat_field(self.db, field) } diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 133fa810d6..c24d196e1b 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -441,14 +441,17 @@ impl SourceAnalyzer { &self, db: &dyn HirDatabase, field: &ast::RecordPatField, - ) -> Option { + ) -> Option<(Field, Type)> { let field_name = field.field_name()?.as_name(); let record_pat = ast::RecordPat::cast(field.syntax().parent().and_then(|p| p.parent())?)?; let pat_id = self.pat_id(&record_pat.into())?; let variant = self.infer.as_ref()?.variant_resolution_for_pat(pat_id)?; let variant_data = variant.variant_data(db.upcast()); let field = FieldId { parent: variant, local_id: variant_data.field(&field_name)? }; - Some(field.into()) + let (_, subst) = self.infer.as_ref()?.type_of_pat.get(pat_id)?.as_adt()?; + let field_ty = + db.field_types(variant).get(field.local_id)?.clone().substitute(Interner, subst); + Some((field.into(), Type::new_with_resolver(db, &self.resolver, field_ty))) } pub(crate) fn resolve_macro_call( diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index 1322f5228e..4071c490b7 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -327,7 +327,7 @@ impl NameClass { let pat_parent = ident_pat.syntax().parent(); if let Some(record_pat_field) = pat_parent.and_then(ast::RecordPatField::cast) { if record_pat_field.name_ref().is_none() { - if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) { + if let Some((field, _)) = sema.resolve_record_pat_field(&record_pat_field) { return Some(NameClass::PatFieldShorthand { local_def: local, field_ref: field, @@ -483,6 +483,13 @@ impl NameRefClass { }, ast::RecordPatField(record_pat_field) => { sema.resolve_record_pat_field(&record_pat_field) + .map(|(field, ..)|field) + .map(Definition::Field) + .map(NameRefClass::Definition) + }, + ast::RecordExprField(record_expr_field) => { + sema.resolve_record_field(&record_expr_field) + .map(|(field, ..)|field) .map(Definition::Field) .map(NameRefClass::Definition) }, diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs index 55cdb3200e..6d2d0bd635 100644 --- a/crates/ide/src/goto_type_definition.rs +++ b/crates/ide/src/goto_type_definition.rs @@ -55,7 +55,7 @@ pub(crate) fn goto_type_definition( ty } else { let record_field = ast::RecordPatField::for_field_name_ref(&it)?; - sema.resolve_record_pat_field(&record_field)?.ty(db) + sema.resolve_record_pat_field(&record_field)?.1 } }, _ => return None, diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index 2c08c457b3..21f92ccddb 100644 --- a/crates/ide/src/signature_help.rs +++ b/crates/ide/src/signature_help.rs @@ -16,7 +16,7 @@ use stdx::format_to; use syntax::{ algo, ast::{self, HasArgList}, - match_ast, AstNode, Direction, SyntaxToken, TextRange, TextSize, + match_ast, AstNode, Direction, SyntaxElementChildren, SyntaxToken, TextRange, TextSize, }; use crate::RootDatabase; @@ -102,6 +102,14 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio } return signature_help_for_record_lit(&sema, record, token); }, + ast::RecordPat(record) => { + let cursor_outside = record.record_pat_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token); + if cursor_outside { + continue; + } + return signature_help_for_record_pat(&sema, record, token); + }, + ast::TupleStructPat(tuple_pat) => {}, _ => (), } } @@ -346,10 +354,27 @@ fn signature_help_for_record_lit( record: ast::RecordExpr, token: SyntaxToken, ) -> Option { - let active_parameter = record - .record_expr_field_list()? - .syntax() - .children_with_tokens() + signature_help_for_record_( + sema, + record.record_expr_field_list()?.syntax().children_with_tokens(), + &record.path()?, + record + .record_expr_field_list()? + .fields() + .filter_map(|field| sema.resolve_record_field(&field)) + .map(|(field, _, ty)| (field, ty)), + token, + ) +} + +fn signature_help_for_record_( + sema: &Semantics<'_, RootDatabase>, + field_list_children: SyntaxElementChildren, + path: &ast::Path, + fields2: impl Iterator, + token: SyntaxToken, +) -> Option { + let active_parameter = field_list_children .filter_map(syntax::NodeOrToken::into_token) .filter(|t| t.kind() == syntax::T![,]) .take_while(|t| t.text_range().start() <= token.text_range().start()) @@ -365,7 +390,7 @@ fn signature_help_for_record_lit( let fields; let db = sema.db; - let path_res = sema.resolve_path(&record.path()?)?; + let path_res = sema.resolve_path(path)?; if let PathResolution::Def(ModuleDef::Variant(variant)) = path_res { fields = variant.fields(db); let en = variant.parent_enum(db); @@ -397,8 +422,7 @@ fn signature_help_for_record_lit( let mut fields = fields.into_iter().map(|field| (field.name(db), Some(field))).collect::>(); let mut buf = String::new(); - for field in record.record_expr_field_list()?.fields() { - let Some((field, _, ty)) = sema.resolve_record_field(&field) else { continue }; + for (field, ty) in fields2 { let name = field.name(db); format_to!(buf, "{name}: {}", ty.display_truncated(db, Some(20))); res.push_record_field(&buf); @@ -418,6 +442,23 @@ fn signature_help_for_record_lit( Some(res) } +fn signature_help_for_record_pat( + sema: &Semantics<'_, RootDatabase>, + record: ast::RecordPat, + token: SyntaxToken, +) -> Option { + signature_help_for_record_( + sema, + record.record_pat_field_list()?.syntax().children_with_tokens(), + &record.path()?, + record + .record_pat_field_list()? + .fields() + .filter_map(|field| sema.resolve_record_pat_field(&field)), + token, + ) +} + #[cfg(test)] mod tests { use std::iter; @@ -1550,6 +1591,29 @@ impl S { ); } + #[test] + fn record_pat() { + check( + r#" +struct Strukt { + t: T, + u: U, + unit: (), +} +fn f() { + let Strukt { + u: 0, + $0 + } +} +"#, + expect![[r#" + struct Strukt { u: i32, t: T, unit: () } + ------ ^^^^ -------- + "#]], + ); + } + #[test] fn test_enum_in_nested_method_in_lambda() { check( From 55120b3d83ff3608b392e3af80491f1eab2cd238 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 15 Mar 2023 10:17:30 +0100 Subject: [PATCH 2/2] Add signature help for tuple struct patterns --- crates/ide/src/signature_help.rs | 223 +++++++++++++++++++++++--- crates/parser/src/grammar/patterns.rs | 15 +- 2 files changed, 213 insertions(+), 25 deletions(-) diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index 21f92ccddb..4b2c139f6f 100644 --- a/crates/ide/src/signature_help.rs +++ b/crates/ide/src/signature_help.rs @@ -109,7 +109,13 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio } return signature_help_for_record_pat(&sema, record, token); }, - ast::TupleStructPat(tuple_pat) => {}, + ast::TupleStructPat(tuple_pat) => { + let cursor_outside = tuple_pat.r_paren_token().as_ref() == Some(&token); + if cursor_outside { + continue; + } + return signature_help_for_tuple_struct_pat(&sema, tuple_pat, token); + }, _ => (), } } @@ -367,6 +373,90 @@ fn signature_help_for_record_lit( ) } +fn signature_help_for_record_pat( + sema: &Semantics<'_, RootDatabase>, + record: ast::RecordPat, + token: SyntaxToken, +) -> Option { + signature_help_for_record_( + sema, + record.record_pat_field_list()?.syntax().children_with_tokens(), + &record.path()?, + record + .record_pat_field_list()? + .fields() + .filter_map(|field| sema.resolve_record_pat_field(&field)), + token, + ) +} + +fn signature_help_for_tuple_struct_pat( + sema: &Semantics<'_, RootDatabase>, + pat: ast::TupleStructPat, + token: SyntaxToken, +) -> Option { + let rest_pat = pat.fields().find(|it| matches!(it, ast::Pat::RestPat(_))); + let is_left_of_rest_pat = + rest_pat.map_or(true, |it| token.text_range().start() < it.syntax().text_range().end()); + + let mut res = SignatureHelp { + doc: None, + signature: String::new(), + parameters: vec![], + active_parameter: None, + }; + + let db = sema.db; + let path_res = sema.resolve_path(&pat.path()?)?; + let fields: Vec<_> = if let PathResolution::Def(ModuleDef::Variant(variant)) = path_res { + let en = variant.parent_enum(db); + + res.doc = en.docs(db).map(|it| it.into()); + format_to!(res.signature, "enum {}::{} (", en.name(db), variant.name(db)); + variant.fields(db) + } else { + let adt = match path_res { + PathResolution::SelfType(imp) => imp.self_ty(db).as_adt()?, + PathResolution::Def(ModuleDef::Adt(adt)) => adt, + _ => return None, + }; + + match adt { + hir::Adt::Struct(it) => { + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "struct {} (", it.name(db)); + it.fields(db) + } + _ => return None, + } + }; + let commas = pat + .syntax() + .children_with_tokens() + .filter_map(syntax::NodeOrToken::into_token) + .filter(|t| t.kind() == syntax::T![,]); + res.active_parameter = Some(if is_left_of_rest_pat { + commas.take_while(|t| t.text_range().start() <= token.text_range().start()).count() + } else { + let n_commas = commas + .collect::>() + .into_iter() + .rev() + .take_while(|t| t.text_range().start() > token.text_range().start()) + .count(); + fields.len().saturating_sub(1).saturating_sub(n_commas) + }); + + let mut buf = String::new(); + for ty in fields.into_iter().map(|it| it.ty(db)) { + format_to!(buf, "{}", ty.display_truncated(db, Some(20))); + res.push_call_param(&buf); + buf.clear(); + } + res.signature.push_str(")"); + Some(res) +} + fn signature_help_for_record_( sema: &Semantics<'_, RootDatabase>, field_list_children: SyntaxElementChildren, @@ -442,23 +532,6 @@ fn signature_help_for_record_( Some(res) } -fn signature_help_for_record_pat( - sema: &Semantics<'_, RootDatabase>, - record: ast::RecordPat, - token: SyntaxToken, -) -> Option { - signature_help_for_record_( - sema, - record.record_pat_field_list()?.syntax().children_with_tokens(), - &record.path()?, - record - .record_pat_field_list()? - .fields() - .filter_map(|field| sema.resolve_record_pat_field(&field)), - token, - ) -} - #[cfg(test)] mod tests { use std::iter; @@ -480,6 +553,7 @@ mod tests { (database, FilePosition { file_id, offset }) } + #[track_caller] fn check(ra_fixture: &str, expect: Expect) { let fixture = format!( r#" @@ -931,6 +1005,119 @@ fn main() { ); } + #[test] + fn tuple_struct_pat() { + check( + r#" +/// A cool tuple struct +struct S(u32, i32); +fn main() { + let S(0, $0); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32) + --- ^^^ + "#]], + ); + } + + #[test] + fn tuple_struct_pat_rest() { + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16); +fn main() { + let S(0, .., $0); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16) + --- --- --- ^^^ + "#]], + ); + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16, u8); +fn main() { + let S(0, .., $0, 0); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16, u8) + --- --- --- ^^^ -- + "#]], + ); + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16); +fn main() { + let S($0, .., 1); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16) + ^^^ --- --- --- + "#]], + ); + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16, u8); +fn main() { + let S(1, .., 1, $0, 2); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16, u8) + --- --- --- ^^^ -- + "#]], + ); + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16); +fn main() { + let S(1, $0.., 1); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16) + --- ^^^ --- --- + "#]], + ); + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16); +fn main() { + let S(1, ..$0, 1); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16) + --- ^^^ --- --- + "#]], + ); + } + #[test] fn generic_struct() { check( diff --git a/crates/parser/src/grammar/patterns.rs b/crates/parser/src/grammar/patterns.rs index abcefffa23..5f4977886f 100644 --- a/crates/parser/src/grammar/patterns.rs +++ b/crates/parser/src/grammar/patterns.rs @@ -431,14 +431,15 @@ fn slice_pat(p: &mut Parser<'_>) -> CompletedMarker { fn pat_list(p: &mut Parser<'_>, ket: SyntaxKind) { while !p.at(EOF) && !p.at(ket) { - if !p.at_ts(PAT_TOP_FIRST) { - p.error("expected a pattern"); - break; - } - pattern_top(p); - if !p.at(ket) { - p.expect(T![,]); + if !p.at(T![,]) { + if p.at_ts(PAT_TOP_FIRST) { + p.error(format!("expected {:?}, got {:?}", T![,], p.current())); + } else { + break; + } + } else { + p.bump(T![,]); } } }