about summary refs log tree commit diff
diff options
context:
space:
mode:
authorOleStrohm <strohm99@gmail.com>2022-08-06 18:50:21 +0200
committerOleStrohm <strohm99@gmail.com>2022-09-12 20:19:13 +0100
commit997fc46efad4dac0c325df55e17a55d7cc4cdc05 (patch)
tree22d596b3f966ea5bdd279076139c5e6ae94400c2
parentf64c95600c0887dbb48a429a00700df47af75eaa (diff)
downloadrust-997fc46efad4dac0c325df55e17a55d7cc4cdc05.tar.gz
rust-997fc46efad4dac0c325df55e17a55d7cc4cdc05.zip
Implemented basic enum const eval
-rw-r--r--crates/hir-def/src/body.rs9
-rw-r--r--crates/hir-def/src/lib.rs11
-rw-r--r--crates/hir-def/src/resolver.rs1
-rw-r--r--crates/hir-ty/src/consteval.rs31
-rw-r--r--crates/hir-ty/src/db.rs16
-rw-r--r--crates/hir-ty/src/diagnostics/unsafe_check.rs4
-rw-r--r--crates/hir-ty/src/infer.rs8
-rw-r--r--crates/hir-ty/src/tests.rs10
-rw-r--r--crates/hir/src/from_id.rs10
-rw-r--r--crates/hir/src/lib.rs16
-rw-r--r--crates/hir/src/symbols.rs5
-rw-r--r--crates/ide-db/src/search.rs1
-rw-r--r--crates/ide/src/hover/render.rs13
-rw-r--r--crates/ide/src/hover/tests.rs109
14 files changed, 227 insertions, 17 deletions
diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs
index 22f5fb99266..484e2d7d7dd 100644
--- a/crates/hir-def/src/body.rs
+++ b/crates/hir-def/src/body.rs
@@ -27,7 +27,7 @@ use crate::{
     macro_id_to_def_id,
     nameres::DefMap,
     path::{ModPath, Path},
-    src::HasSource,
+    src::{HasChildSource, HasSource},
     AsMacroCall, BlockId, DefWithBodyId, HasModule, LocalModuleId, Lookup, MacroId, ModuleId,
     UnresolvedMacro,
 };
@@ -324,6 +324,13 @@ impl Body {
                 let src = s.source(db);
                 (src.file_id, s.module(db), src.value.body())
             }
+            DefWithBodyId::VariantId(v) => {
+                let e = v.parent.lookup(db);
+                let src = v.parent.child_source(db);
+                let variant = &src.value[v.local_id];
+                // TODO(ole): Handle missing exprs (+1 to the prev)
+                (src.file_id, e.container, variant.expr())
+            }
         };
         let expander = Expander::new(db, file_id, module);
         let (mut body, source_map) = Body::new(db, expander, params, body);
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index 32ebfda4fd9..4c44840e861 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -474,16 +474,25 @@ pub enum DefWithBodyId {
     FunctionId(FunctionId),
     StaticId(StaticId),
     ConstId(ConstId),
+    VariantId(EnumVariantId),
 }
 
 impl_from!(FunctionId, ConstId, StaticId for DefWithBodyId);
 
+// FIXME: Rename EnumVariantId to VariantId so that the macro above can be used
+impl From<EnumVariantId> for DefWithBodyId {
+    fn from(id: EnumVariantId) -> Self {
+        DefWithBodyId::VariantId(id)
+    }
+}
+
 impl DefWithBodyId {
     pub fn as_generic_def_id(self) -> Option<GenericDefId> {
         match self {
             DefWithBodyId::FunctionId(f) => Some(f.into()),
             DefWithBodyId::StaticId(_) => None,
             DefWithBodyId::ConstId(c) => Some(c.into()),
+            DefWithBodyId::VariantId(c) => Some(c.into()),
         }
     }
 }
@@ -681,6 +690,7 @@ impl HasModule for DefWithBodyId {
             DefWithBodyId::FunctionId(it) => it.lookup(db).module(db),
             DefWithBodyId::StaticId(it) => it.lookup(db).module(db),
             DefWithBodyId::ConstId(it) => it.lookup(db).module(db),
+            DefWithBodyId::VariantId(it) => it.parent.lookup(db).container,
         }
     }
 }
@@ -691,6 +701,7 @@ impl DefWithBodyId {
             DefWithBodyId::FunctionId(it) => it.lookup(db).id.value.into(),
             DefWithBodyId::StaticId(it) => it.lookup(db).id.value.into(),
             DefWithBodyId::ConstId(it) => it.lookup(db).id.value.into(),
+            DefWithBodyId::VariantId(it) => it.parent.lookup(db).id.value.into(),
         }
     }
 }
diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs
index 8aa5973cac5..070f6837133 100644
--- a/crates/hir-def/src/resolver.rs
+++ b/crates/hir-def/src/resolver.rs
@@ -839,6 +839,7 @@ impl HasResolver for DefWithBodyId {
             DefWithBodyId::ConstId(c) => c.resolver(db),
             DefWithBodyId::FunctionId(f) => f.resolver(db),
             DefWithBodyId::StaticId(s) => s.resolver(db),
+            DefWithBodyId::VariantId(v) => v.parent.resolver(db),
         }
     }
 }
diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs
index 6ecb6e6fd17..e934fe1c323 100644
--- a/crates/hir-ty/src/consteval.rs
+++ b/crates/hir-ty/src/consteval.rs
@@ -11,7 +11,7 @@ use hir_def::{
     path::ModPath,
     resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs},
     type_ref::ConstScalar,
-    ConstId, DefWithBodyId,
+    ConstId, DefWithBodyId, EnumVariantId,
 };
 use la_arena::{Arena, Idx};
 use stdx::never;
@@ -339,6 +339,7 @@ pub fn eval_const(
                 ValueNs::GenericParam(_) => {
                     Err(ConstEvalError::NotSupported("const generic without substitution"))
                 }
+                ValueNs::EnumVariantId(id) => ctx.db.const_eval_variant(id), // TODO(ole): Assuming this is all that has to happen?
                 _ => Err(ConstEvalError::NotSupported("path that are not const or local")),
             }
         }
@@ -412,6 +413,14 @@ pub(crate) fn const_eval_recover(
     Err(ConstEvalError::Loop)
 }
 
+pub(crate) fn const_eval_recover_variant(
+    _: &dyn HirDatabase,
+    _: &[String],
+    _: &EnumVariantId,
+) -> Result<ComputedExpr, ConstEvalError> {
+    Err(ConstEvalError::Loop)
+}
+
 pub(crate) fn const_eval_query(
     db: &dyn HirDatabase,
     const_id: ConstId,
@@ -433,6 +442,26 @@ pub(crate) fn const_eval_query(
     result
 }
 
+pub(crate) fn const_eval_query_variant(
+    db: &dyn HirDatabase,
+    variant_id: EnumVariantId,
+) -> Result<ComputedExpr, ConstEvalError> {
+    let def = variant_id.into();
+    let body = db.body(def);
+    let infer = &db.infer(def);
+    eval_const(
+        body.body_expr,
+        &mut ConstEvalCtx {
+            db,
+            owner: def,
+            exprs: &body.exprs,
+            pats: &body.pats,
+            local_data: HashMap::default(),
+            infer,
+        },
+    )
+}
+
 pub(crate) fn eval_to_const<'a>(
     expr: Idx<Expr>,
     mode: ParamLoweringMode,
diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs
index dd5639f00d2..79c5c01ec65 100644
--- a/crates/hir-ty/src/db.rs
+++ b/crates/hir-ty/src/db.rs
@@ -6,8 +6,9 @@ use std::sync::Arc;
 use arrayvec::ArrayVec;
 use base_db::{impl_intern_key, salsa, CrateId, Upcast};
 use hir_def::{
-    db::DefDatabase, expr::ExprId, BlockId, ConstId, ConstParamId, DefWithBodyId, FunctionId,
-    GenericDefId, ImplId, LifetimeParamId, LocalFieldId, TypeOrConstParamId, VariantId,
+    db::DefDatabase, expr::ExprId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumVariantId,
+    FunctionId, GenericDefId, ImplId, LifetimeParamId, LocalFieldId, Lookup, TypeOrConstParamId,
+    VariantId,
 };
 use la_arena::ArenaMap;
 
@@ -47,6 +48,10 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     #[salsa::cycle(crate::consteval::const_eval_recover)]
     fn const_eval(&self, def: ConstId) -> Result<ComputedExpr, ConstEvalError>;
 
+    #[salsa::invoke(crate::consteval::const_eval_query_variant)]
+    #[salsa::cycle(crate::consteval::const_eval_recover_variant)]
+    fn const_eval_variant(&self, def: EnumVariantId) -> Result<ComputedExpr, ConstEvalError>;
+
     #[salsa::invoke(crate::lower::impl_trait_query)]
     fn impl_trait(&self, def: ImplId) -> Option<Binders<TraitRef>>;
 
@@ -188,6 +193,13 @@ fn infer_wait(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<InferenceResult>
         DefWithBodyId::ConstId(it) => {
             db.const_data(it).name.clone().unwrap_or_else(Name::missing).to_string()
         }
+        DefWithBodyId::VariantId(it) => {
+            let up_db: &dyn DefDatabase = db.upcast();
+            let loc = it.parent.lookup(up_db);
+            let item_tree = loc.id.item_tree(up_db);
+            let konst = &item_tree[loc.id.value];
+            konst.name.to_string()
+        }
     });
     db.infer_query(def)
 }
diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs
index 161b19a739c..431ab949b46 100644
--- a/crates/hir-ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs
@@ -18,7 +18,9 @@ pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
 
     let is_unsafe = match def {
         DefWithBodyId::FunctionId(it) => db.function_data(it).has_unsafe_kw(),
-        DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
+        DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) | DefWithBodyId::VariantId(_) => {
+            false
+        }
     };
     if is_unsafe {
         return res;
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index e37763e8ea7..63d0f1b01cf 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -67,6 +67,14 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<Infer
         DefWithBodyId::ConstId(c) => ctx.collect_const(&db.const_data(c)),
         DefWithBodyId::FunctionId(f) => ctx.collect_fn(f),
         DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_data(s)),
+        DefWithBodyId::VariantId(v) => {
+            //let def = AttrDefId::EnumVariantId(v);
+            //let attrs = db.attrs(def);
+            //let repr = attrs.by_key("repr").attrs().next().unwrap();
+            //let ident = repr.single_ident_value().unwrap().text;
+            // TODO(ole): Get the real type
+            ctx.return_ty = TyBuilder::def_ty(db, v.parent.into()).fill_with_unknown().build()
+        }
     }
 
     ctx.infer_body();
diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs
index d2f13e4351c..be5ece9c5c5 100644
--- a/crates/hir-ty/src/tests.rs
+++ b/crates/hir-ty/src/tests.rs
@@ -16,7 +16,7 @@ use base_db::{fixture::WithFixture, FileRange, SourceDatabaseExt};
 use expect_test::Expect;
 use hir_def::{
     body::{Body, BodySourceMap, SyntheticSyntax},
-    db::DefDatabase,
+    db::{DefDatabase, InternDatabase},
     expr::{ExprId, PatId},
     item_scope::ItemScope,
     nameres::DefMap,
@@ -135,6 +135,10 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
             let loc = it.lookup(&db);
             loc.source(&db).value.syntax().text_range().start()
         }
+        DefWithBodyId::VariantId(it) => {
+            let loc = db.lookup_intern_enum(it.parent);
+            loc.source(&db).value.syntax().text_range().start()
+        }
     });
     let mut unexpected_type_mismatches = String::new();
     for def in defs {
@@ -388,6 +392,10 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
             let loc = it.lookup(&db);
             loc.source(&db).value.syntax().text_range().start()
         }
+        DefWithBodyId::VariantId(it) => {
+            let loc = db.lookup_intern_enum(it.parent);
+            loc.source(&db).value.syntax().text_range().start()
+        }
     });
     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 9c7558d1918..f825a72c0f5 100644
--- a/crates/hir/src/from_id.rs
+++ b/crates/hir/src/from_id.rs
@@ -140,6 +140,7 @@ impl From<DefWithBody> for DefWithBodyId {
             DefWithBody::Function(it) => DefWithBodyId::FunctionId(it.id),
             DefWithBody::Static(it) => DefWithBodyId::StaticId(it.id),
             DefWithBody::Const(it) => DefWithBodyId::ConstId(it.id),
+            DefWithBody::Variant(it) => DefWithBodyId::VariantId(it.into()),
         }
     }
 }
@@ -150,6 +151,7 @@ impl From<DefWithBodyId> for DefWithBody {
             DefWithBodyId::FunctionId(it) => DefWithBody::Function(it.into()),
             DefWithBodyId::StaticId(it) => DefWithBody::Static(it.into()),
             DefWithBodyId::ConstId(it) => DefWithBody::Const(it.into()),
+            DefWithBodyId::VariantId(it) => DefWithBody::Variant(it.into()),
         }
     }
 }
@@ -172,9 +174,7 @@ impl From<GenericDef> for GenericDefId {
             GenericDef::Trait(it) => GenericDefId::TraitId(it.id),
             GenericDef::TypeAlias(it) => GenericDefId::TypeAliasId(it.id),
             GenericDef::Impl(it) => GenericDefId::ImplId(it.id),
-            GenericDef::Variant(it) => {
-                GenericDefId::EnumVariantId(EnumVariantId { parent: it.parent.id, local_id: it.id })
-            }
+            GenericDef::Variant(it) => GenericDefId::EnumVariantId(it.into()),
             GenericDef::Const(it) => GenericDefId::ConstId(it.id),
         }
     }
@@ -188,9 +188,7 @@ impl From<GenericDefId> for GenericDef {
             GenericDefId::TraitId(it) => GenericDef::Trait(it.into()),
             GenericDefId::TypeAliasId(it) => GenericDef::TypeAlias(it.into()),
             GenericDefId::ImplId(it) => GenericDef::Impl(it.into()),
-            GenericDefId::EnumVariantId(it) => {
-                GenericDef::Variant(Variant { parent: it.parent.into(), id: it.local_id })
-            }
+            GenericDefId::EnumVariantId(it) => GenericDef::Variant(it.into()),
             GenericDefId::ConstId(it) => GenericDef::Const(it.into()),
         }
     }
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 7dd891c86e8..258224a7584 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -73,7 +73,7 @@ use once_cell::unsync::Lazy;
 use rustc_hash::FxHashSet;
 use stdx::{impl_from, never};
 use syntax::{
-    ast::{self, HasAttrs as _, HasDocComments, HasName},
+    ast::{self, Expr, HasAttrs as _, HasDocComments, HasName},
     AstNode, AstPtr, SmolStr, SyntaxNodePtr, TextRange, T,
 };
 
@@ -962,11 +962,16 @@ impl HasVisibility for Enum {
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct Variant {
-    pub(crate) parent: Enum,
-    pub(crate) id: LocalEnumVariantId,
+    pub parent: Enum,
+    pub id: LocalEnumVariantId,
 }
 
 impl Variant {
+    pub fn value(self, db: &dyn HirDatabase) -> Option<Expr> {
+        // TODO(ole): Handle missing exprs (+1 to the prev)
+        self.source(db)?.value.expr()
+    }
+
     pub fn module(self, db: &dyn HirDatabase) -> Module {
         self.parent.module(db)
     }
@@ -1129,6 +1134,7 @@ pub enum DefWithBody {
     Function(Function),
     Static(Static),
     Const(Const),
+    Variant(Variant),
 }
 impl_from!(Function, Const, Static for DefWithBody);
 
@@ -1138,6 +1144,7 @@ impl DefWithBody {
             DefWithBody::Const(c) => c.module(db),
             DefWithBody::Function(f) => f.module(db),
             DefWithBody::Static(s) => s.module(db),
+            DefWithBody::Variant(v) => v.module(db),
         }
     }
 
@@ -1146,6 +1153,7 @@ impl DefWithBody {
             DefWithBody::Function(f) => Some(f.name(db)),
             DefWithBody::Static(s) => Some(s.name(db)),
             DefWithBody::Const(c) => c.name(db),
+            DefWithBody::Variant(v) => Some(v.name(db)),
         }
     }
 
@@ -1155,6 +1163,7 @@ impl DefWithBody {
             DefWithBody::Function(it) => it.ret_type(db),
             DefWithBody::Static(it) => it.ty(db),
             DefWithBody::Const(it) => it.ty(db),
+            DefWithBody::Variant(it) => it.parent.ty(db),
         }
     }
 
@@ -1379,6 +1388,7 @@ impl DefWithBody {
             DefWithBody::Function(it) => it.into(),
             DefWithBody::Static(it) => it.into(),
             DefWithBody::Const(it) => it.into(),
+            DefWithBody::Variant(it) => it.into(),
         };
         for diag in hir_ty::diagnostics::incorrect_case(db, krate, def.into()) {
             acc.push(diag.into())
diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs
index 616a406c727..8432f0e7e01 100644
--- a/crates/hir/src/symbols.rs
+++ b/crates/hir/src/symbols.rs
@@ -1,6 +1,7 @@
 //! File symbol extraction.
 
 use base_db::FileRange;
+use hir_def::db::DefDatabase;
 use hir_def::{
     item_tree::ItemTreeNode, src::HasSource, AdtId, AssocItemId, AssocItemLoc, DefWithBodyId,
     HasModule, ImplId, ItemContainerId, Lookup, MacroId, ModuleDefId, ModuleId, TraitId,
@@ -244,6 +245,10 @@ impl<'a> SymbolCollector<'a> {
             DefWithBodyId::ConstId(id) => Some(
                 id.lookup(self.db.upcast()).source(self.db.upcast()).value.name()?.text().into(),
             ),
+            DefWithBodyId::VariantId(id) => Some({
+                let up_db: &dyn DefDatabase = self.db.upcast();
+                up_db.lookup_intern_enum(id.parent).source(up_db).value.name()?.text().into()
+            }),
         }
     }
 
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index 7deffe8e0f6..20ab474e802 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -236,6 +236,7 @@ impl Definition {
                 DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
                 DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
                 DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
+                DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()),
             };
             return match def {
                 Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index c5c50d88dd2..cd63131e7a7 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -2,7 +2,9 @@
 use std::fmt::Display;
 
 use either::Either;
-use hir::{AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
+use hir::{
+    db::HirDatabase, AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo,
+};
 use ide_db::{
     base_db::SourceDatabase,
     defs::Definition,
@@ -346,7 +348,14 @@ pub(super) fn definition(
         Definition::Module(it) => label_and_docs(db, it),
         Definition::Function(it) => label_and_docs(db, it),
         Definition::Adt(it) => label_and_docs(db, it),
-        Definition::Variant(it) => label_and_docs(db, it),
+        Definition::Variant(it) => label_value_and_docs(db, it, |&it| {
+            let hir_db: &dyn HirDatabase = db;
+            let body = hir_db.const_eval_variant(it.into());
+            match body {
+                Ok(x) => Some(format!("{}", x)),
+                Err(_) => it.value(db).map(|s| format!("{}", s)),
+            }
+        }),
         Definition::Const(it) => label_value_and_docs(db, it, |it| {
             let body = it.eval(db);
             match body {
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 4b8b47783d1..f24dec25b6b 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -3528,6 +3528,86 @@ impl<const LEN: usize> Foo<LEN$0> {}
 }
 
 #[test]
+fn hover_const_eval_variant() {
+    // show hex for <10
+    check(
+        r#"
+#[repr(u8)]
+enum E {
+    /// This is a doc
+    A$0 = 1 << 3,
+}
+"#,
+        expect![[r#"
+            *A*
+
+            ```rust
+            test::E
+            ```
+
+            ```rust
+            A = 8
+            ```
+
+            ---
+
+            This is a doc
+        "#]],
+    );
+    // show hex for >10
+    check(
+        r#"
+#[repr(u8)]
+enum E {
+    /// This is a doc
+    A$0 = (1 << 3) + (1 << 2),
+}
+"#,
+        expect![[r#"
+            *A*
+
+            ```rust
+            test::E
+            ```
+
+            ```rust
+            A = 12 (0xC)
+            ```
+
+            ---
+
+            This is a doc
+        "#]],
+    );
+    // enums in const eval
+    check(
+        r#"
+#[repr(u8)]
+enum E {
+    A = 1,
+    /// This is a doc
+    B$0 = E::A + 1,
+}
+"#,
+        expect![[r#"
+            *B*
+
+            ```rust
+            test::E
+            ```
+
+            ```rust
+            B = 2
+            ```
+
+            ---
+
+            This is a doc
+        "#]],
+    );
+}
+
+#[test]
 fn hover_const_eval() {
     // show hex for <10
     check(
@@ -3823,6 +3903,35 @@ fn foo() {
             This is a doc
         "#]],
     );
+    check(
+        r#"
+enum E {
+    /// This is a doc
+    A = 3,
+}
+fn foo(e: E) {
+    match e {
+        E::A$0 => (),
+        _ => ()
+    }
+}
+"#,
+        expect![[r#"
+            *A*
+
+            ```rust
+            test::E
+            ```
+
+            ```rust
+            A = 3
+            ```
+
+            ---
+
+            This is a doc
+        "#]],
+    );
 }
 
 #[test]