From 7de0b2e75a541b98f735ee6fcd12d326be38d23f Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Thu, 23 Jan 2025 00:27:31 +0900 Subject: [PATCH] feat: Implement `default-field-values` --- crates/hir-def/src/expr_store/body.rs | 3 + crates/hir-def/src/expr_store/lower.rs | 2 + crates/hir-def/src/expr_store/pretty.rs | 27 ++++++ crates/hir-def/src/lib.rs | 59 ++++++++++++- .../builtin_derive_macro.rs | 42 +++++++++ crates/hir-def/src/resolver.rs | 10 +++ crates/hir-expand/src/builtin/derive_macro.rs | 86 +++++++++++++++++-- crates/hir-ty/src/diagnostics/unsafe_check.rs | 3 +- crates/hir-ty/src/infer.rs | 16 ++++ crates/hir-ty/src/mir/lower.rs | 4 + crates/hir-ty/src/mir/pretty.rs | 34 +++++++- crates/hir-ty/src/tests.rs | 2 + crates/hir/src/from_id.rs | 2 + crates/hir/src/lib.rs | 64 +++++++++++++- crates/ide-db/src/defs.rs | 1 + crates/ide-db/src/search.rs | 6 ++ .../src/handlers/type_mismatch.rs | 15 ++++ crates/parser/src/grammar/expressions.rs | 18 +++- crates/parser/src/grammar/items/adt.rs | 5 ++ crates/parser/test_data/generated/runner.rs | 12 +++ .../comma_after_default_values_syntax.rast | 59 +++++++++++++ .../err/comma_after_default_values_syntax.rs | 4 + ...cord_literal_before_ellipsis_recovery.rast | 70 ++++++++++++--- ...record_literal_before_ellipsis_recovery.rs | 2 + .../ok/record_field_default_values.rast | 28 ++++++ .../inline/ok/record_field_default_values.rs | 1 + .../parser/inline/ok/record_lit.rast | 47 ++++++++++ .../test_data/parser/inline/ok/record_lit.rs | 2 + .../ok/struct_initializer_with_defaults.rast | 39 +++++++++ .../ok/struct_initializer_with_defaults.rs | 3 + .../rust-analyzer/src/cli/analysis_stats.rs | 6 ++ crates/syntax/rust.ungram | 2 +- crates/syntax/src/ast/generated/nodes.rs | 4 + 33 files changed, 647 insertions(+), 31 deletions(-) create mode 100644 crates/parser/test_data/parser/inline/err/comma_after_default_values_syntax.rast create mode 100644 crates/parser/test_data/parser/inline/err/comma_after_default_values_syntax.rs create mode 100644 crates/parser/test_data/parser/inline/ok/record_field_default_values.rast create mode 100644 crates/parser/test_data/parser/inline/ok/record_field_default_values.rs create mode 100644 crates/parser/test_data/parser/inline/ok/struct_initializer_with_defaults.rast create mode 100644 crates/parser/test_data/parser/inline/ok/struct_initializer_with_defaults.rs diff --git a/crates/hir-def/src/expr_store/body.rs b/crates/hir-def/src/expr_store/body.rs index a55fec4f8b..3177b2c74a 100644 --- a/crates/hir-def/src/expr_store/body.rs +++ b/crates/hir-def/src/expr_store/body.rs @@ -122,6 +122,9 @@ impl Body { src.map(|it| it.expr()) } DefWithBodyId::InTypeConstId(c) => c.lookup(db).id.map(|_| c.source(db).expr()), + DefWithBodyId::FieldId(f) => { + f.record_field_source(db).map(|it| it.and_then(|it| it.expr())) + } } }; let module = def.module(db); diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs index dfc716eb84..0d3a542a43 100644 --- a/crates/hir-def/src/expr_store/lower.rs +++ b/crates/hir-def/src/expr_store/lower.rs @@ -90,6 +90,7 @@ pub(super) fn lower_body( DefWithBodyId::ConstId(it) => db.attrs(it.into()), DefWithBodyId::InTypeConstId(_) => Attrs::EMPTY, DefWithBodyId::VariantId(it) => db.attrs(it.into()), + DefWithBodyId::FieldId(it) => db.attrs(it.into()), } .rust_analyzer_tool() .any(|attr| *attr.path() == tool_path![skip]); @@ -168,6 +169,7 @@ pub(super) fn lower_body( Awaitable::No("constant") } DefWithBodyId::VariantId(..) => Awaitable::No("enum variant"), + DefWithBodyId::FieldId(..) => Awaitable::No("field"), } }, ); diff --git a/crates/hir-def/src/expr_store/pretty.rs b/crates/hir-def/src/expr_store/pretty.rs index 6a0b1e5197..6ba0bbd61c 100644 --- a/crates/hir-def/src/expr_store/pretty.rs +++ b/crates/hir-def/src/expr_store/pretty.rs @@ -11,6 +11,7 @@ use crate::{ Statement, }, pretty::{print_generic_args, print_path, print_type_ref}, + VariantId, }; use super::*; @@ -56,6 +57,32 @@ pub(super) fn print_body_hir( loc.id.item_tree(db)[loc.id.value].name.display(db.upcast(), edition), ) } + DefWithBodyId::FieldId(it) => { + let parent_name: String = match it.parent { + VariantId::EnumVariantId(it) => { + let loc = it.lookup(db); + let enum_loc = loc.parent.lookup(db); + format!( + "{}::{}", + enum_loc.id.item_tree(db)[enum_loc.id.value] + .name + .display(db.upcast(), edition), + loc.id.item_tree(db)[loc.id.value].name.display(db.upcast(), edition), + ) + } + VariantId::StructId(it) => it + .lookup(db) + .id + .resolved(db, |it| it.name.display(db.upcast(), edition).to_string()), + VariantId::UnionId(it) => it + .lookup(db) + .id + .resolved(db, |it| it.name.display(db.upcast(), edition).to_string()), + }; + let variant_data = it.parent.variant_data(db); + let field_name = &variant_data.fields()[it.local_id].name; + format!("field {}.{}", parent_name, field_name.display(db.upcast(), edition),) + } }; let mut p = Printer { diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index c8efd90432..95700b54db 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -55,6 +55,7 @@ pub mod visibility; use intern::Interned; pub use rustc_abi as layout; +use src::HasSource; use triomphe::Arc; #[cfg(test)] @@ -77,6 +78,7 @@ use hir_expand::{ builtin::{BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, eager::expand_eager_macro_input, + files::InFileWrapper, impl_intern_lookup, name::Name, proc_macro::{CustomProcMacroExpander, ProcMacroKind}, @@ -519,6 +521,41 @@ pub struct FieldId { pub local_id: LocalFieldId, } +impl FieldId { + pub fn record_field_source( + &self, + db: &dyn DefDatabase, + ) -> InFileWrapper> { + let field_list = match self.parent { + crate::VariantId::EnumVariantId(it) => { + let s = it.lookup(db); + s.source(db).map(|it| { + it.field_list().and_then(|it| match it { + ast::FieldList::RecordFieldList(it) => Some(it), + _ => None, + }) + }) + } + crate::VariantId::StructId(it) => { + let s = it.lookup(db); + s.source(db).map(|it| { + it.field_list().and_then(|it| match it { + ast::FieldList::RecordFieldList(it) => Some(it), + _ => None, + }) + }) + } + crate::VariantId::UnionId(it) => { + let s = it.lookup(db); + s.source(db).map(|it| it.record_field_list()) + } + }; + field_list.map(|it| { + it.and_then(|it| it.fields().nth(self.local_id.into_raw().into_u32() as usize)) + }) + } +} + pub type LocalFieldId = Idx; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -686,6 +723,7 @@ pub enum TypeOwnerId { TypeAliasId(TypeAliasId), ImplId(ImplId), EnumVariantId(EnumVariantId), + FieldId(FieldId), } impl TypeOwnerId { @@ -703,6 +741,11 @@ impl TypeOwnerId { GenericDefId::AdtId(AdtId::EnumId(it.lookup(db).parent)) } TypeOwnerId::InTypeConstId(_) => return None, + TypeOwnerId::FieldId(it) => GenericDefId::AdtId(match it.parent { + VariantId::EnumVariantId(it) => AdtId::EnumId(it.lookup(db).parent), + VariantId::StructId(it) => it.into(), + VariantId::UnionId(it) => it.into(), + }), }) } } @@ -717,7 +760,8 @@ impl_from!( TraitAliasId, TypeAliasId, ImplId, - EnumVariantId + EnumVariantId, + FieldId for TypeOwnerId ); @@ -730,6 +774,7 @@ impl From for TypeOwnerId { DefWithBodyId::ConstId(it) => it.into(), DefWithBodyId::InTypeConstId(it) => it.into(), DefWithBodyId::VariantId(it) => it.into(), + DefWithBodyId::FieldId(it) => it.into(), } } } @@ -885,6 +930,7 @@ pub enum DefWithBodyId { ConstId(ConstId), InTypeConstId(InTypeConstId), VariantId(EnumVariantId), + FieldId(FieldId), } impl_from!(FunctionId, ConstId, StaticId, InTypeConstId for DefWithBodyId); @@ -905,6 +951,7 @@ impl DefWithBodyId { // FIXME: stable rust doesn't allow generics in constants, but we should // use `TypeOwnerId::as_generic_def_id` when it does. DefWithBodyId::InTypeConstId(_) => None, + DefWithBodyId::FieldId(_) => None, } } } @@ -1332,6 +1379,11 @@ impl HasModule for TypeOwnerId { TypeOwnerId::ImplId(it) => it.module(db), TypeOwnerId::EnumVariantId(it) => it.module(db), TypeOwnerId::InTypeConstId(it) => it.lookup(db).owner.module(db), + TypeOwnerId::FieldId(it) => match it.parent { + VariantId::EnumVariantId(it) => it.module(db), + VariantId::StructId(it) => it.module(db), + VariantId::UnionId(it) => it.module(db), + }, } } } @@ -1344,6 +1396,11 @@ impl HasModule for DefWithBodyId { DefWithBodyId::ConstId(it) => it.module(db), DefWithBodyId::VariantId(it) => it.module(db), DefWithBodyId::InTypeConstId(it) => it.lookup(db).owner.module(db), + DefWithBodyId::FieldId(it) => match it.parent { + VariantId::EnumVariantId(it) => it.module(db), + VariantId::StructId(it) => it.module(db), + VariantId::UnionId(it) => it.module(db), + }, } } } diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs index c31d322132..25391c910e 100644 --- a/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs +++ b/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs @@ -211,6 +211,20 @@ enum Bar { #[default] Bar, } +#[derive(Default)] +struct Baz { + field1: i32 = 2, + field2: bool = { false }, +} +#[derive(Default)] +enum Qux { + #[default] + Foo { + field1: i32, + field2: bool = true, + field3: (), + } +} "#, expect![[r#" #[derive(Default)] @@ -224,6 +238,20 @@ enum Bar { #[default] Bar, } +#[derive(Default)] +struct Baz { + field1: i32 = 2, + field2: bool = { false }, +} +#[derive(Default)] +enum Qux { + #[default] + Foo { + field1: i32, + field2: bool = true, + field3: (), + } +} impl <> $crate::default::Default for Foo< > where { fn default() -> Self { @@ -236,6 +264,20 @@ impl <> $crate::default::Default for Bar< > where { fn default() -> Self { Bar::Bar } +} +impl <> $crate::default::Default for Baz< > where { + fn default() -> Self { + Baz { + .. + } + } +} +impl <> $crate::default::Default for Qux< > where { + fn default() -> Self { + Qux::Foo { + field1: $crate::default::Default::default(), field3: $crate::default::Default::default(), .. + } + } }"#]], ); } diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 7e13ae2f7a..5299894296 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -1227,6 +1227,11 @@ impl HasResolver for TypeOwnerId { TypeOwnerId::TypeAliasId(it) => it.resolver(db), TypeOwnerId::ImplId(it) => it.resolver(db), TypeOwnerId::EnumVariantId(it) => it.resolver(db), + TypeOwnerId::FieldId(it) => match it.parent { + VariantId::EnumVariantId(it) => it.resolver(db), + VariantId::StructId(it) => it.resolver(db), + VariantId::UnionId(it) => it.resolver(db), + }, } } } @@ -1239,6 +1244,11 @@ impl HasResolver for DefWithBodyId { DefWithBodyId::StaticId(s) => s.resolver(db), DefWithBodyId::VariantId(v) => v.resolver(db), DefWithBodyId::InTypeConstId(c) => c.lookup(db).owner.resolver(db), + DefWithBodyId::FieldId(f) => match f.parent { + VariantId::EnumVariantId(it) => it.resolver(db), + VariantId::StructId(it) => it.resolver(db), + VariantId::UnionId(it) => it.resolver(db), + }, } } } diff --git a/crates/hir-expand/src/builtin/derive_macro.rs b/crates/hir-expand/src/builtin/derive_macro.rs index 28b6812139..f8fb700d55 100644 --- a/crates/hir-expand/src/builtin/derive_macro.rs +++ b/crates/hir-expand/src/builtin/derive_macro.rs @@ -80,9 +80,15 @@ pub fn find_builtin_derive(ident: &name::Name) -> Option BuiltinDeriveExpander::find_by_name(ident) } +#[derive(Clone, Copy)] +enum HasDefault { + Yes, + No, +} + #[derive(Clone)] enum VariantShape { - Struct(Vec), + Struct(Vec<(tt::Ident, HasDefault)>), Tuple(usize), Unit, } @@ -98,7 +104,7 @@ impl VariantShape { fn field_names(&self, span: Span) -> Vec { match self { - VariantShape::Struct(s) => s.clone(), + VariantShape::Struct(s) => s.iter().map(|(ident, _)| ident.clone()).collect(), VariantShape::Tuple(n) => tuple_field_iterator(span, *n).collect(), VariantShape::Unit => vec![], } @@ -112,7 +118,7 @@ impl VariantShape { ) -> tt::TopSubtree { match self { VariantShape::Struct(fields) => { - let fields = fields.iter().map(|it| { + let fields = fields.iter().map(|(it, _)| { let mapped = field_map(it); quote! {span => #it : #mapped , } }); @@ -135,6 +141,63 @@ impl VariantShape { } } + fn default_expand( + &self, + path: tt::TopSubtree, + span: Span, + field_map: impl Fn(&tt::Ident) -> tt::TopSubtree, + ) -> tt::TopSubtree { + match self { + VariantShape::Struct(fields) => { + let contains_default = fields.iter().any(|it| matches!(it.1, HasDefault::Yes)); + let fields = fields + .iter() + .filter_map(|(it, has_default)| match has_default { + HasDefault::Yes => None, + HasDefault::No => Some(it), + }) + .map(|it| { + let mapped = field_map(it); + quote! {span => #it : #mapped , } + }); + if contains_default { + let mut double_dots = + tt::TopSubtreeBuilder::new(tt::Delimiter::invisible_spanned(span)); + double_dots.push(tt::Leaf::Punct(tt::Punct { + char: '.', + spacing: tt::Spacing::Joint, + span, + })); + double_dots.push(tt::Leaf::Punct(tt::Punct { + char: '.', + spacing: tt::Spacing::Alone, + span, + })); + let double_dots = double_dots.build(); + quote! {span => + #path { ##fields #double_dots } + } + } else { + quote! {span => + #path { ##fields } + } + } + } + &VariantShape::Tuple(n) => { + let fields = tuple_field_iterator(span, n).map(|it| { + let mapped = field_map(&it); + quote! {span => + #mapped , + } + }); + quote! {span => + #path ( ##fields ) + } + } + VariantShape::Unit => path, + } + } + fn from( call_site: Span, tm: &ExpansionSpanMap, @@ -144,8 +207,15 @@ impl VariantShape { None => VariantShape::Unit, Some(FieldList::RecordFieldList(it)) => VariantShape::Struct( it.fields() - .map(|it| it.name()) - .map(|it| name_to_token(call_site, tm, it)) + .map(|it| { + ( + it.name(), + if it.expr().is_some() { HasDefault::Yes } else { HasDefault::No }, + ) + }) + .map(|(it, has_default)| { + name_to_token(call_site, tm, it).map(|ident| (ident, has_default)) + }) .collect::>()?, ), Some(FieldList::TupleFieldList(it)) => VariantShape::Tuple(it.fields().count()), @@ -601,7 +671,7 @@ fn default_expand( let body = match &adt.shape { AdtShape::Struct(fields) => { let name = &adt.name; - fields.as_pattern_map( + fields.default_expand( quote!(span =>#name), span, |_| quote!(span =>#krate::default::Default::default()), @@ -611,7 +681,7 @@ fn default_expand( if let Some(d) = default_variant { let (name, fields) = &variants[*d]; let adt_name = &adt.name; - fields.as_pattern_map( + fields.default_expand( quote!(span =>#adt_name :: #name), span, |_| quote!(span =>#krate::default::Default::default()), @@ -643,7 +713,7 @@ fn debug_expand( expand_simple_derive(db, span, tt, quote! {span => #krate::fmt::Debug }, |adt| { let for_variant = |name: String, v: &VariantShape| match v { VariantShape::Struct(fields) => { - let for_fields = fields.iter().map(|it| { + let for_fields = fields.iter().map(|(it, _)| { let x_string = it.to_string(); quote! {span => .field(#x_string, & #it) diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs index 99ec1951a3..03218b4691 100644 --- a/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -33,7 +33,8 @@ pub fn missing_unsafe( DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) | DefWithBodyId::VariantId(_) - | DefWithBodyId::InTypeConstId(_) => false, + | DefWithBodyId::InTypeConstId(_) + | DefWithBodyId::FieldId(_) => false, }; let body = db.body(def); diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 3c258e3c4c..1b2ef2aef3 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -134,6 +134,9 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc { + ctx.collect_field(f); + } } ctx.infer_body(); @@ -910,6 +913,19 @@ impl<'a> InferenceContext<'a> { self.return_ty = return_ty; } + fn collect_field(&mut self, field: FieldId) { + let variant_data = field.parent.variant_data(self.db.upcast()); + let field_data = &variant_data.fields()[field.local_id]; + let types_map = variant_data.types_map(); + let return_ty = + self.make_ty(field_data.type_ref, types_map, InferenceTyDiagnosticSource::Signature); + + // Field default value exprs might be defining usage sites of TAITs. + self.make_tait_coercion_table(iter::once(&return_ty)); + + self.return_ty = return_ty; + } + fn collect_fn(&mut self, func: FunctionId) { let data = self.db.function_data(func); let mut param_tys = diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index cc6ed122af..23072011a7 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -2130,6 +2130,10 @@ pub fn mir_body_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Result format!("in type const {it:?}"), + DefWithBodyId::FieldId(it) => it.parent.variant_data(db.upcast()).fields()[it.local_id] + .name + .display(db.upcast(), edition) + .to_string(), }; let _p = tracing::info_span!("mir_body_query", ?detail).entered(); let body = db.body(def); diff --git a/crates/hir-ty/src/mir/pretty.rs b/crates/hir-ty/src/mir/pretty.rs index 2a26101ac4..193b7bcd97 100644 --- a/crates/hir-ty/src/mir/pretty.rs +++ b/crates/hir-ty/src/mir/pretty.rs @@ -6,7 +6,7 @@ use std::{ }; use either::Either; -use hir_def::{expr_store::Body, hir::BindingId}; +use hir_def::{expr_store::Body, hir::BindingId, VariantId}; use hir_expand::{name::Name, Lookup}; use la_arena::ArenaMap; use span::Edition; @@ -79,6 +79,38 @@ impl MirBody { hir_def::DefWithBodyId::InTypeConstId(id) => { w!(this, "in type const {id:?} = "); } + hir_def::DefWithBodyId::FieldId(id) => { + w!(this, "field "); + match id.parent { + VariantId::EnumVariantId(it) => { + let loc = it.lookup(db.upcast()); + let enum_loc = loc.parent.lookup(db.upcast()); + w!( + this, + "{}::{}", + enum_loc.id.item_tree(db.upcast())[enum_loc.id.value] + .name + .display(db.upcast(), Edition::LATEST), + loc.id.item_tree(db.upcast())[loc.id.value] + .name + .display(db.upcast(), Edition::LATEST), + ); + } + VariantId::StructId(id) => { + id.lookup(db.upcast()).id.resolved(db.upcast(), |it| { + w!(this, "{}", it.name.display(db.upcast(), Edition::LATEST)); + }); + } + VariantId::UnionId(id) => { + id.lookup(db.upcast()).id.resolved(db.upcast(), |it| { + w!(this, "{}", it.name.display(db.upcast(), Edition::LATEST)); + }); + } + }; + let variant_data = id.parent.variant_data(db.upcast()); + let field_name = &variant_data.fields()[id.local_id].name; + w!(this, ".{}: _ = ", field_name.display(db.upcast(), Edition::LATEST)); + } }); ctx.result } diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index 69ec35f406..a5af712b42 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -160,6 +160,7 @@ fn check_impl( loc.source(&db).value.syntax().text_range().start() } DefWithBodyId::InTypeConstId(it) => it.source(&db).syntax().text_range().start(), + DefWithBodyId::FieldId(_) => unreachable!(), }); let mut unexpected_type_mismatches = String::new(); for def in defs { @@ -415,6 +416,7 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { loc.source(&db).value.syntax().text_range().start() } DefWithBodyId::InTypeConstId(it) => it.source(&db).syntax().text_range().start(), + DefWithBodyId::FieldId(_) => unreachable!(), }); for def in defs { let (body, source_map) = db.body_with_source_map(def); diff --git a/crates/hir/src/from_id.rs b/crates/hir/src/from_id.rs index 537401afdc..dd26e894d7 100644 --- a/crates/hir/src/from_id.rs +++ b/crates/hir/src/from_id.rs @@ -147,6 +147,7 @@ impl From for DefWithBodyId { DefWithBody::Const(it) => DefWithBodyId::ConstId(it.id), DefWithBody::Variant(it) => DefWithBodyId::VariantId(it.into()), DefWithBody::InTypeConst(it) => DefWithBodyId::InTypeConstId(it.id), + DefWithBody::Field(it) => DefWithBodyId::FieldId(it.into()), } } } @@ -159,6 +160,7 @@ impl From for DefWithBody { DefWithBodyId::ConstId(it) => DefWithBody::Const(it.into()), DefWithBodyId::VariantId(it) => DefWithBody::Variant(it.into()), DefWithBodyId::InTypeConstId(it) => DefWithBody::InTypeConst(it.into()), + DefWithBodyId::FieldId(it) => DefWithBody::Field(it.into()), } } } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 27723cbc16..8a3aa14047 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -415,6 +415,19 @@ impl ModuleDef { def.diagnostics(db, &mut acc); } + let fields = match self { + ModuleDef::Adt(Adt::Struct(it)) => Some(it.fields(db)), + ModuleDef::Adt(Adt::Union(it)) => Some(it.fields(db)), + ModuleDef::Variant(it) => Some(it.fields(db)), + _ => None, + }; + if let Some(fields) = fields { + for field in fields { + let def: DefWithBody = field.into(); + def.diagnostics(db, &mut acc, style_lints); + } + } + acc } @@ -1226,6 +1239,12 @@ impl HasVisibility for Module { } } +impl From<&Field> for DefWithBodyId { + fn from(&f: &Field) -> Self { + DefWithBodyId::FieldId(f.into()) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Field { pub(crate) parent: VariantDef, @@ -1291,6 +1310,10 @@ impl AstNode for FieldSource { } impl Field { + pub fn module(self, db: &dyn HirDatabase) -> Module { + self.parent.module(db) + } + pub fn name(&self, db: &dyn HirDatabase) -> Name { self.parent.variant_data(db).fields()[self.id].name.clone() } @@ -1353,6 +1376,14 @@ impl Field { pub fn parent_def(&self, _db: &dyn HirDatabase) -> VariantDef { self.parent } + + pub fn default_value_source( + &self, + db: &dyn HirDatabase, + ) -> Option> { + let id: hir_def::FieldId = (*self).into(); + id.record_field_source(db.upcast()).map(|it| it.and_then(|it| it.expr())).transpose() + } } impl HasVisibility for Field { @@ -1789,8 +1820,9 @@ pub enum DefWithBody { Const(Const), Variant(Variant), InTypeConst(InTypeConst), + Field(Field), } -impl_from!(Function, Const, Static, Variant, InTypeConst for DefWithBody); +impl_from!(Function, Const, Static, Variant, InTypeConst, Field for DefWithBody); impl DefWithBody { pub fn module(self, db: &dyn HirDatabase) -> Module { @@ -1800,6 +1832,7 @@ impl DefWithBody { DefWithBody::Static(s) => s.module(db), DefWithBody::Variant(v) => v.module(db), DefWithBody::InTypeConst(c) => c.module(db), + DefWithBody::Field(f) => f.module(db), } } @@ -1810,6 +1843,7 @@ impl DefWithBody { DefWithBody::Const(c) => c.name(db), DefWithBody::Variant(v) => Some(v.name(db)), DefWithBody::InTypeConst(_) => None, + DefWithBody::Field(f) => Some(f.name(db)), } } @@ -1825,6 +1859,7 @@ impl DefWithBody { &DefWithBodyId::from(it.id).resolver(db.upcast()), TyKind::Error.intern(Interner), ), + DefWithBody::Field(it) => it.ty(db), } } @@ -1835,6 +1870,7 @@ impl DefWithBody { DefWithBody::Const(it) => it.id.into(), DefWithBody::Variant(it) => it.into(), DefWithBody::InTypeConst(it) => it.id.into(), + DefWithBody::Field(it) => it.into(), } } @@ -1880,6 +1916,23 @@ impl DefWithBody { item_tree_source_maps.konst(konst.value) } DefWithBody::Variant(_) | DefWithBody::InTypeConst(_) => &TypesSourceMap::EMPTY, + DefWithBody::Field(field) => match field.parent { + VariantDef::Struct(strukt) => { + let strukt = strukt.id.lookup(db.upcast()).id; + item_tree_source_maps = strukt.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.strukt(strukt.value).item() + } + VariantDef::Union(union) => { + let union = union.id.lookup(db.upcast()).id; + item_tree_source_maps = union.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.union(union.value).item() + } + VariantDef::Variant(variant) => { + let variant = variant.id.lookup(db.upcast()).id; + item_tree_source_maps = variant.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.variant(variant.value) + } + }, }; for (_, def_map) in body.blocks(db.upcast()) { @@ -2111,8 +2164,8 @@ impl DefWithBody { DefWithBody::Static(it) => it.into(), DefWithBody::Const(it) => it.into(), DefWithBody::Variant(it) => it.into(), - // FIXME: don't ignore diagnostics for in type const - DefWithBody::InTypeConst(_) => return, + // FIXME: don't ignore diagnostics for in type const and default field value exprs + DefWithBody::InTypeConst(_) | DefWithBody::Field(_) => return, }; for diag in hir_ty::diagnostics::incorrect_case(db, def.into()) { acc.push(diag.into()) @@ -3237,7 +3290,10 @@ impl AsAssocItem for DefWithBody { match self { DefWithBody::Function(it) => it.as_assoc_item(db), DefWithBody::Const(it) => it.as_assoc_item(db), - DefWithBody::Static(_) | DefWithBody::Variant(_) | DefWithBody::InTypeConst(_) => None, + DefWithBody::Static(_) + | DefWithBody::Variant(_) + | DefWithBody::InTypeConst(_) + | DefWithBody::Field(_) => None, } } } diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index bad5360805..6925880ba9 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -972,6 +972,7 @@ impl TryFrom for Definition { DefWithBody::Const(it) => Ok(it.into()), DefWithBody::Variant(it) => Ok(it.into()), DefWithBody::InTypeConst(_) => Err(()), + DefWithBody::Field(it) => Ok(it.into()), } } } diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index 7963e8ae4f..d2a237a5c0 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -310,6 +310,9 @@ impl Definition { DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()), // FIXME: implement DefWithBody::InTypeConst(_) => return SearchScope::empty(), + DefWithBody::Field(f) => { + f.default_value_source(db).map(|src| src.syntax().cloned()) + } }; return match def { Some(def) => SearchScope::file_range( @@ -327,6 +330,9 @@ impl Definition { DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()), // FIXME: implement DefWithBody::InTypeConst(_) => return SearchScope::empty(), + DefWithBody::Field(f) => { + f.default_value_source(db).map(|src| src.syntax().cloned()) + } }; return match def { Some(def) => SearchScope::file_range( diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 56afb38cc8..7bc1be3822 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -1232,6 +1232,21 @@ fn f() { let (_, _, _, ..) = (true, 42); // ^^^^^^^^^^^^^ error: expected (bool, i32), found (bool, i32, {unknown}) } +"#, + ); + } + + #[test] + fn diagnostics_inside_field_default_expr() { + check_diagnostics( + r#" +struct Foo { + foo: i32 = { + let x = false; + x + // ^ error: expected i32, found bool + }, +} "#, ); } diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs index 389c01933c..fe1316c9bf 100644 --- a/crates/parser/src/grammar/expressions.rs +++ b/crates/parser/src/grammar/expressions.rs @@ -678,6 +678,8 @@ fn path_expr(p: &mut Parser<'_>, r: Restrictions) -> (CompletedMarker, BlockLike // S { x }; // S { x, y: 32, }; // S { x, y: 32, ..Default::default() }; +// S { x, y: 32, .. }; +// S { .. }; // S { x: ::default() }; // TupleStruct { 0: 1 }; // } @@ -709,6 +711,8 @@ pub(crate) fn record_expr_field_list(p: &mut Parser<'_>) { // fn main() { // S { field ..S::default() } // S { 0 ..S::default() } + // S { field .. } + // S { 0 .. } // } name_ref_or_index(p); p.error("expected `:`"); @@ -739,7 +743,13 @@ pub(crate) fn record_expr_field_list(p: &mut Parser<'_>) { // S { .. } = S {}; // } - // We permit `.. }` on the left-hand side of a destructuring assignment. + // test struct_initializer_with_defaults + // fn foo() { + // let _s = S { .. }; + // } + + // We permit `.. }` on the left-hand side of a destructuring assignment + // or defaults values. if !p.at(T!['}']) { expr(p); @@ -750,6 +760,12 @@ pub(crate) fn record_expr_field_list(p: &mut Parser<'_>) { // S { ..x, a: 0 } // } + // test_err comma_after_default_values_syntax + // fn foo() { + // S { .., }; + // S { .., a: 0 } + // } + // Do not bump, so we can support additional fields after this comma. p.error("cannot use a comma after the base struct"); } diff --git a/crates/parser/src/grammar/items/adt.rs b/crates/parser/src/grammar/items/adt.rs index 21078175c0..9a16c9db6d 100644 --- a/crates/parser/src/grammar/items/adt.rs +++ b/crates/parser/src/grammar/items/adt.rs @@ -135,6 +135,11 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) { name(p); p.expect(T![:]); types::type_(p); + // test record_field_default_values + // struct S { f: f32 = 0.0 } + if p.eat(T![=]) { + expressions::expr(p); + } m.complete(p, RECORD_FIELD); } else { m.abandon(p); diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index b9f87b6af2..c8ea8c547a 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -482,6 +482,10 @@ mod ok { run_and_expect_no_errors("test_data/parser/inline/ok/record_field_attrs.rs"); } #[test] + fn record_field_default_values() { + run_and_expect_no_errors("test_data/parser/inline/ok/record_field_default_values.rs"); + } + #[test] fn record_field_list() { run_and_expect_no_errors("test_data/parser/inline/ok/record_field_list.rs"); } @@ -544,6 +548,10 @@ mod ok { run_and_expect_no_errors("test_data/parser/inline/ok/stmt_postfix_expr_ambiguity.rs"); } #[test] + fn struct_initializer_with_defaults() { + run_and_expect_no_errors("test_data/parser/inline/ok/struct_initializer_with_defaults.rs"); + } + #[test] fn struct_item() { run_and_expect_no_errors("test_data/parser/inline/ok/struct_item.rs"); } #[test] fn trait_alias() { run_and_expect_no_errors("test_data/parser/inline/ok/trait_alias.rs"); } @@ -719,6 +727,10 @@ mod err { ); } #[test] + fn comma_after_default_values_syntax() { + run_and_expect_errors("test_data/parser/inline/err/comma_after_default_values_syntax.rs"); + } + #[test] fn crate_visibility_empty_recover() { run_and_expect_errors("test_data/parser/inline/err/crate_visibility_empty_recover.rs"); } diff --git a/crates/parser/test_data/parser/inline/err/comma_after_default_values_syntax.rast b/crates/parser/test_data/parser/inline/err/comma_after_default_values_syntax.rast new file mode 100644 index 0000000000..feb617e1aa --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/comma_after_default_values_syntax.rast @@ -0,0 +1,59 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + DOT2 ".." + ERROR + COMMA "," + WHITESPACE " " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n " + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + DOT2 ".." + ERROR + COMMA "," + WHITESPACE " " + RECORD_EXPR_FIELD + NAME_REF + IDENT "a" + COLON ":" + WHITESPACE " " + LITERAL + INT_NUMBER "0" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" +error 21: expected expression +error 36: expected expression +error 37: expected COMMA diff --git a/crates/parser/test_data/parser/inline/err/comma_after_default_values_syntax.rs b/crates/parser/test_data/parser/inline/err/comma_after_default_values_syntax.rs new file mode 100644 index 0000000000..f1ecdf89fa --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/comma_after_default_values_syntax.rs @@ -0,0 +1,4 @@ +fn foo() { + S { .., }; + S { .., a: 0 } +} diff --git a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast index 08ae906421..12b4e233e3 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast @@ -44,6 +44,56 @@ SOURCE_FILE WHITESPACE " " R_CURLY "}" WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + RECORD_EXPR_FIELD + NAME_REF + INT_NUMBER "0" + WHITESPACE " " + DOT2 ".." + CALL_EXPR + PATH_EXPR + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "default" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + RECORD_EXPR_FIELD + NAME_REF + IDENT "field" + WHITESPACE " " + DOT2 ".." + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n " RECORD_EXPR PATH PATH_SEGMENT @@ -58,20 +108,6 @@ SOURCE_FILE INT_NUMBER "0" WHITESPACE " " DOT2 ".." - CALL_EXPR - PATH_EXPR - PATH - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "default" - ARG_LIST - L_PAREN "(" - R_PAREN ")" WHITESPACE " " R_CURLY "}" WHITESPACE "\n" @@ -82,3 +118,9 @@ error 25: expected COMMA error 42: expected SEMICOLON error 52: expected `:` error 52: expected COMMA +error 69: expected SEMICOLON +error 83: expected `:` +error 83: expected COMMA +error 88: expected SEMICOLON +error 98: expected `:` +error 98: expected COMMA diff --git a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs index 65398ccb88..416cd763fd 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs +++ b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs @@ -1,4 +1,6 @@ fn main() { S { field ..S::default() } S { 0 ..S::default() } + S { field .. } + S { 0 .. } } diff --git a/crates/parser/test_data/parser/inline/ok/record_field_default_values.rast b/crates/parser/test_data/parser/inline/ok/record_field_default_values.rast new file mode 100644 index 0000000000..33088f2cab --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/record_field_default_values.rast @@ -0,0 +1,28 @@ +SOURCE_FILE + STRUCT + STRUCT_KW "struct" + WHITESPACE " " + NAME + IDENT "S" + WHITESPACE " " + RECORD_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + RECORD_FIELD + NAME + IDENT "f" + COLON ":" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "f32" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + FLOAT_NUMBER "0.0" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/record_field_default_values.rs b/crates/parser/test_data/parser/inline/ok/record_field_default_values.rs new file mode 100644 index 0000000000..d7b38944a8 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/record_field_default_values.rs @@ -0,0 +1 @@ +struct S { f: f32 = 0.0 } diff --git a/crates/parser/test_data/parser/inline/ok/record_lit.rast b/crates/parser/test_data/parser/inline/ok/record_lit.rast index 00948c322f..b868da55bc 100644 --- a/crates/parser/test_data/parser/inline/ok/record_lit.rast +++ b/crates/parser/test_data/parser/inline/ok/record_lit.rast @@ -120,6 +120,53 @@ SOURCE_FILE R_CURLY "}" SEMICOLON ";" WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + RECORD_EXPR_FIELD + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + COMMA "," + WHITESPACE " " + RECORD_EXPR_FIELD + NAME_REF + IDENT "y" + COLON ":" + WHITESPACE " " + LITERAL + INT_NUMBER "32" + COMMA "," + WHITESPACE " " + DOT2 ".." + WHITESPACE " " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + DOT2 ".." + WHITESPACE " " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n " EXPR_STMT RECORD_EXPR PATH diff --git a/crates/parser/test_data/parser/inline/ok/record_lit.rs b/crates/parser/test_data/parser/inline/ok/record_lit.rs index 86411fbb7d..42895f759b 100644 --- a/crates/parser/test_data/parser/inline/ok/record_lit.rs +++ b/crates/parser/test_data/parser/inline/ok/record_lit.rs @@ -3,6 +3,8 @@ fn foo() { S { x }; S { x, y: 32, }; S { x, y: 32, ..Default::default() }; + S { x, y: 32, .. }; + S { .. }; S { x: ::default() }; TupleStruct { 0: 1 }; } diff --git a/crates/parser/test_data/parser/inline/ok/struct_initializer_with_defaults.rast b/crates/parser/test_data/parser/inline/ok/struct_initializer_with_defaults.rast new file mode 100644 index 0000000000..987e219ae8 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/struct_initializer_with_defaults.rast @@ -0,0 +1,39 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + LET_STMT + LET_KW "let" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "_s" + WHITESPACE " " + EQ "=" + WHITESPACE " " + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + DOT2 ".." + WHITESPACE " " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/struct_initializer_with_defaults.rs b/crates/parser/test_data/parser/inline/ok/struct_initializer_with_defaults.rs new file mode 100644 index 0000000000..e08204f94c --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/struct_initializer_with_defaults.rs @@ -0,0 +1,3 @@ +fn foo() { + let _s = S { .. }; +} diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index cd709afe09..824f262ca3 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -673,6 +673,9 @@ impl flags::AnalysisStats { DefWithBody::Const(it) => it.source(db).map(|it| it.syntax().cloned()), DefWithBody::Variant(it) => it.source(db).map(|it| it.syntax().cloned()), DefWithBody::InTypeConst(_) => unimplemented!(), + DefWithBody::Field(it) => { + it.default_value_source(db).map(|it| it.syntax().cloned()) + } }; if let Some(src) = source { let original_file = src.file_id.original_file(db); @@ -987,6 +990,9 @@ impl flags::AnalysisStats { DefWithBody::Const(it) => it.source(db).map(|it| it.syntax().cloned()), DefWithBody::Variant(it) => it.source(db).map(|it| it.syntax().cloned()), DefWithBody::InTypeConst(_) => unimplemented!(), + DefWithBody::Field(it) => { + it.default_value_source(db).map(|it| it.syntax().cloned()) + } }; if let Some(src) = source { let original_file = src.file_id.original_file(db); diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index 4e2a70d6cd..bbb8413cbc 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -241,7 +241,7 @@ RecordFieldList = RecordField = Attr* Visibility? - Name ':' Type + Name ':' Type ('=' Expr)? TupleFieldList = '(' fields:(TupleField (',' TupleField)* ','?)? ')' diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index 69e2a9f9c1..8f10ea9464 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -1538,10 +1538,14 @@ impl ast::HasDocComments for RecordField {} impl ast::HasName for RecordField {} impl ast::HasVisibility for RecordField {} impl RecordField { + #[inline] + pub fn expr(&self) -> Option { support::child(&self.syntax) } #[inline] pub fn ty(&self) -> Option { support::child(&self.syntax) } #[inline] pub fn colon_token(&self) -> Option { support::token(&self.syntax, T![:]) } + #[inline] + pub fn eq_token(&self) -> Option { support::token(&self.syntax, T![=]) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)]