Repository: rust-lang/rust-analyzer
Pull request #12150 (2022-05-04 15:51:34)

fix: doc url link type
fix: #12033

I did some debugging and found the cause looks like to be some doc links' `LinkType` are kept as `Shortcut` which don't make sense for url links.
This PR should resolve both problems in the origin issue, but aside this PR, more work are needed for doc_links.

about `LinkType`: https://github.com/raphlinus/pulldown-cmark/blob/f29bd1e228913690e5092c9594e4e607423ff0aa/src/lib.rs#L191-L210


Patch:
diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs
index 11d3f48b94..fd44493076 100644
--- a/crates/hir-def/src/attr.rs
+++ b/crates/hir-def/src/attr.rs
@@ -853,6 +853,25 @@ impl<'attr> AttrQuery<'attr> {
             .iter()
             .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_smol_str() == key))
     }
+
+    /// Find string value for a specific key inside token tree
+    ///
+    /// ```ignore
+    /// #[doc(html_root_url = "url")]
+    ///       ^^^^^^^^^^^^^ key
+    /// ```
+    pub fn find_string_value_in_tt(self, key: &'attr str) -> Option<&SmolStr> {
+        self.tt_values().find_map(|tt| {
+            let name = tt.token_trees.iter()
+                .skip_while(|tt| !matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text, ..} )) if text == key))
+                .nth(2);
+
+            match name {
+                Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal{ref text, ..}))) => Some(text),
+                _ => None
+            }
+        })
+    }
 }
 
 fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs {
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index ac81b8140b..54a04f0ba2 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -75,7 +75,6 @@ use syntax::{
     ast::{self, HasAttrs as _, HasDocComments, HasName},
     AstNode, AstPtr, SmolStr, SyntaxNodePtr, T,
 };
-use tt::{Ident, Leaf, Literal, TokenTree};
 
 use crate::db::{DefDatabase, HirDatabase};
 
@@ -230,23 +229,7 @@ impl Crate {
     pub fn get_html_root_url(self: &Crate, db: &dyn HirDatabase) -> Option<String> {
         // Look for #![doc(html_root_url = "...")]
         let attrs = db.attrs(AttrDefId::ModuleId(self.root_module(db).into()));
-        let doc_attr_q = attrs.by_key("doc");
-
-        if !doc_attr_q.exists() {
-            return None;
-        }
-
-        let doc_url = doc_attr_q.tt_values().filter_map(|tt| {
-            let name = tt.token_trees.iter()
-                .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident { text, ..} )) if text == "html_root_url"))
-                .nth(2);
-
-            match name {
-                Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text),
-                _ => None
-            }
-        }).next();
-
+        let doc_url = attrs.by_key("doc").find_string_value_in_tt("html_root_url");
         doc_url.map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/")
     }
 
@@ -1780,10 +1763,6 @@ impl BuiltinType {
         matches!(self.inner, hir_def::builtin_type::BuiltinType::Char)
     }
 
-    pub fn is_bool(&self) -> bool {
-        matches!(self.inner, hir_def::builtin_type::BuiltinType::Bool)
-    }
-
     pub fn is_str(&self) -> bool {
         matches!(self.inner, hir_def::builtin_type::BuiltinType::Str)
     }
diff --git a/crates/ide-assists/src/handlers/reorder_impl_items.rs b/crates/ide-assists/src/handlers/reorder_impl.rs
similarity index 51%
rename from crates/ide-assists/src/handlers/reorder_impl_items.rs
rename to crates/ide-assists/src/handlers/reorder_impl.rs
index 2bda3a1817..c0991c6080 100644
--- a/crates/ide-assists/src/handlers/reorder_impl_items.rs
+++ b/crates/ide-assists/src/handlers/reorder_impl.rs
@@ -6,46 +6,46 @@ use syntax::{
     ted, AstNode,
 };
 
-use crate::{AssistContext, AssistId, AssistKind, Assists};
+use crate::{utils::get_methods, AssistContext, AssistId, AssistKind, Assists};
 
-// Assist: reorder_impl_items
+// Assist: reorder_impl
 //
-// Reorder the items of an `impl Trait`. The items will be ordered
+// Reorder the methods of an `impl Trait`. The methods will be ordered
 // in the same order as in the trait definition.
 //
 // ```
 // trait Foo {
-//     type A;
-//     const B: u8;
-//     fn c();
+//     fn a() {}
+//     fn b() {}
+//     fn c() {}
 // }
 //
 // struct Bar;
 // $0impl Foo for Bar {
-//     const B: u8 = 17;
+//     fn b() {}
 //     fn c() {}
-//     type A = String;
+//     fn a() {}
 // }
 // ```
 // ->
 // ```
 // trait Foo {
-//     type A;
-//     const B: u8;
-//     fn c();
+//     fn a() {}
+//     fn b() {}
+//     fn c() {}
 // }
 //
 // struct Bar;
 // impl Foo for Bar {
-//     type A = String;
-//     const B: u8 = 17;
+//     fn a() {}
+//     fn b() {}
 //     fn c() {}
 // }
 // ```
-pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?;
     let items = impl_ast.assoc_item_list()?;
-    let assoc_items = items.assoc_items().collect::<Vec<_>>();
+    let methods = get_methods(&items);
 
     let path = impl_ast
         .trait_()
@@ -55,37 +55,29 @@ pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext) -> Opti
         })?
         .path()?;
 
-    let ranks = compute_item_ranks(&path, ctx)?;
-    let sorted: Vec<_> = assoc_items
+    let ranks = compute_method_ranks(&path, ctx)?;
+    let sorted: Vec<_> = methods
         .iter()
         .cloned()
-        .sorted_by_key(|i| {
-            let name = match i {
-                ast::AssocItem::Const(c) => c.name(),
-                ast::AssocItem::Fn(f) => f.name(),
-                ast::AssocItem::TypeAlias(t) => t.name(),
-                ast::AssocItem::MacroCall(_) => None,
-            };
-
-            name.and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::max_value())
+        .sorted_by_key(|f| {
+            f.name().and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::max_value())
         })
         .collect();
 
     // Don't edit already sorted methods:
-    if assoc_items == sorted {
+    if methods == sorted {
         cov_mark::hit!(not_applicable_if_sorted);
         return None;
     }
 
     let target = items.syntax().text_range();
     acc.add(
-        AssistId("reorder_impl_items", AssistKind::RefactorRewrite),
-        "Sort items by trait definition",
+        AssistId("reorder_impl", AssistKind::RefactorRewrite),
+        "Sort methods by trait definition",
         target,
         |builder| {
-            let assoc_items =
-                assoc_items.into_iter().map(|item| builder.make_mut(item)).collect::<Vec<_>>();
-            assoc_items
+            let methods = methods.into_iter().map(|fn_| builder.make_mut(fn_)).collect::<Vec<_>>();
+            methods
                 .into_iter()
                 .zip(sorted)
                 .for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax()));
@@ -93,15 +85,18 @@ pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext) -> Opti
     )
 }
 
-fn compute_item_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
+fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
     let td = trait_definition(path, &ctx.sema)?;
 
     Some(
         td.items(ctx.db())
             .iter()
-            .flat_map(|i| i.name(ctx.db()))
+            .flat_map(|i| match i {
+                hir::AssocItem::Function(f) => Some(f),
+                _ => None,
+            })
             .enumerate()
-            .map(|(idx, name)| (name.to_string(), idx))
+            .map(|(idx, func)| (func.name(ctx.db()).to_string(), idx))
             .collect(),
     )
 }
@@ -123,19 +118,15 @@ mod tests {
     fn not_applicable_if_sorted() {
         cov_mark::check!(not_applicable_if_sorted);
         check_assist_not_applicable(
-            reorder_impl_items,
+            reorder_impl,
             r#"
 trait Bar {
-    type T;
-    const C: ();
     fn a() {}
     fn z() {}
     fn b() {}
 }
 struct Foo;
 $0impl Bar for Foo {
-    type T = ();
-    const C: () = ();
     fn a() {}
     fn z() {}
     fn b() {}
@@ -144,49 +135,10 @@ $0impl Bar for Foo {
         )
     }
 
-    #[test]
-    fn reorder_impl_trait_functions() {
-        check_assist(
-            reorder_impl_items,
-            r#"
-trait Bar {
-    fn a() {}
-    fn c() {}
-    fn b() {}
-    fn d() {}
-}
-
-struct Foo;
-$0impl Bar for Foo {
-    fn d() {}
-    fn b() {}
-    fn c() {}
-    fn a() {}
-}
-"#,
-            r#"
-trait Bar {
-    fn a() {}
-    fn c() {}
-    fn b() {}
-    fn d() {}
-}
-
-struct Foo;
-impl Bar for Foo {
-    fn a() {}
-    fn c() {}
-    fn b() {}
-    fn d() {}
-}
-"#,
-        )
-    }
-
     #[test]
     fn not_applicable_if_empty() {
         check_assist_not_applicable(
-            reorder_impl_items,
+            reorder_impl,
             r#"
 trait Bar {};
 struct Foo;
@@ -196,85 +148,69 @@ $0impl Bar for Foo {}
     }
 
     #[test]
-    fn reorder_impl_trait_items() {
+    fn reorder_impl_trait_functions() {
         check_assist(
-            reorder_impl_items,
+            reorder_impl,
             r#"
 trait Bar {
     fn a() {}
-    type T0;
     fn c() {}
-    const C1: ();
     fn b() {}
-    type T1;
     fn d() {}
-    const C0: ();
 }
 
 struct Foo;
 $0impl Bar for Foo {
-    type T1 = ();
     fn d() {}
     fn b() {}
     fn c() {}
-    const C1: () = ();
     fn a() {}
-    type T0 = ();
-    const C0: () = ();
 }
         "#,
             r#"
 trait Bar {
     fn a() {}
-    type T0;
     fn c() {}
-    const C1: ();
     fn b() {}
-    type T1;
     fn d() {}
-    const C0: ();
 }
 
 struct Foo;
 impl Bar for Foo {
     fn a() {}
-    type T0 = ();
     fn c() {}
-    const C1: () = ();
     fn b() {}
-    type T1 = ();
     fn d() {}
-    const C0: () = ();
 }
         "#,
         )
     }
 
     #[test]
-    fn reorder_impl_trait_items_uneven_ident_lengths() {
+    fn reorder_impl_trait_methods_uneven_ident_lengths() {
         check_assist(
-            reorder_impl_items,
+            reorder_impl,
             r#"
 trait Bar {
-    type Foo;
-    type Fooo;
+    fn foo(&mut self) {}
+    fn fooo(&mut self) {}
 }
 
 struct Foo;
 impl Bar for Foo {
-    type Fooo = ();
-    type Foo = ();$0
+    fn fooo(&mut self) {}
+    fn foo(&mut self) {$0}
 }"#,
             r#"
 trait Bar {
-    type Foo;
-    type Fooo;
+    fn foo(&mut self) {}
+    fn fooo(&mut self) {}
 }
 
 struct Foo;
 impl Bar for Foo {
-    type Foo = ();
-    type Fooo = ();
+    fn foo(&mut self) {}
+    fn fooo(&mut self) {}
 }"#,
         )
     }
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index 42bbc70b53..ef4aa1c62b 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -37,7 +37,7 @@
 //!   should be available more or less everywhere, which isn't useful. So
 //!   instead we only show it if the user *selects* the items they want to sort.
 //! * Consider grouping related assists together (see [`Assists::add_group`]).
-//! * Make assists robust. If the assist depends on results of type-inference too
+//! * Make assists robust. If the assist depends on results of type-inference to
 //!   much, it might only fire in fully-correct code. This makes assist less
 //!   useful and (worse) less predictable. The user should have a clear
 //!   intuition when each particular assist is available.
@@ -54,6 +54,7 @@
 //!   something. If something *could* be a diagnostic, it should be a
 //!   diagnostic. Conversely, it might be valuable to turn a diagnostic with a
 //!   lot of false errors into an assist.
+//! *
 //!
 //! See also this post:
 //! <https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html>
@@ -169,7 +170,7 @@ mod handlers {
     mod remove_mut;
     mod remove_unused_param;
     mod reorder_fields;
-    mod reorder_impl_items;
+    mod reorder_impl;
     mod replace_try_expr_with_match;
     mod replace_derive_with_manual_impl;
     mod replace_if_let_with_match;
@@ -256,7 +257,7 @@ mod handlers {
             remove_mut::remove_mut,
             remove_unused_param::remove_unused_param,
             reorder_fields::reorder_fields,
-            reorder_impl_items::reorder_impl_items,
+            reorder_impl::reorder_impl,
             replace_try_expr_with_match::replace_try_expr_with_match,
             replace_derive_with_manual_impl::replace_derive_with_manual_impl,
             replace_if_let_with_match::replace_if_let_with_match,
diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs
index 891547aaef..3160004380 100644
--- a/crates/ide-diagnostics/src/handlers/missing_fields.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs
@@ -172,9 +172,6 @@ fn get_default_constructor(
         if builtin_ty.is_str() {
             return Some(make::ext::empty_str());
         }
-        if builtin_ty.is_bool() {
-            return Some(make::ext::default_bool());
-        }
     }
 
     let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate();
@@ -195,13 +192,10 @@ fn get_default_constructor(
         })
         .is_some();
 
-    let famous_defs = FamousDefs(&ctx.sema, krate);
     if has_new_func {
         Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module)))
-    } else if ty.as_adt() == famous_defs.core_option_Option()?.ty(ctx.sema.db).as_adt() {
-        Some(make::ext::option_none())
     } else if !ty.is_array()
-        && ty.impls_trait(ctx.sema.db, famous_defs.core_default_Default()?, &[])
+        && ty.impls_trait(ctx.sema.db, FamousDefs(&ctx.sema, krate).core_default_Default()?, &[])
     {
         Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module)))
     } else {
@@ -301,18 +295,17 @@ pub struct Foo { pub a: i32, pub b: i32 }
     fn test_fill_struct_fields_empty() {
         check_fix(
             r#"
-//- minicore: option
-struct TestStruct { one: i32, two: i64, three: Option<i32>, four: bool }
+struct TestStruct { one: i32, two: i64 }
 
 fn test_fn() {
     let s = TestStruct {$0};
 }
 "#,
             r#"
-struct TestStruct { one: i32, two: i64, three: Option<i32>, four: bool }
+struct TestStruct { one: i32, two: i64 }
 
 fn test_fn() {
-    let s = TestStruct { one: 0, two: 0, three: None, four: false };
+    let s = TestStruct { one: 0, two: 0 };
 }
 "#,
         );
@@ -422,7 +415,7 @@ fn test_fn() {
     fn test_fill_struct_fields_default() {
         check_fix(
             r#"
-//- minicore: default, option
+//- minicore: default
 struct TestWithDefault(usize);
 impl Default for TestWithDefault {
     pub fn default() -> Self {
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index a1e170634b..9ad11b7a65 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -45,19 +45,19 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: Defin
         // and valid URLs so we choose to be too eager to try to resolve what might be
         // a URL.
         if target.contains("://") {
-            (target.to_string(), title.to_string())
+            (Some(LinkType::Inline), target.to_string(), title.to_string())
         } else {
             // Two possibilities:
             // * path-based links: `../../module/struct.MyStruct.html`
             // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
-            if let Some(rewritten) = rewrite_intra_doc_link(db, definition, target, title) {
-                return rewritten;
+            if let Some((target, title)) = rewrite_intra_doc_link(db, definition, target, title) {
+                return (None, target, title);
             }
             if let Some(target) = rewrite_url_link(db, definition, target) {
-                return (target, title.to_string());
+                return (Some(LinkType::Inline), target, title.to_string());
             }
 
-            (target.to_string(), title.to_string())
+            (None, target.to_string(), title.to_string())
         }
     });
     let mut out = String::new();
@@ -368,33 +368,42 @@ fn mod_path_of_def(db: &RootDatabase, def: Definition) -> Option<String> {
 /// Rewrites a markdown document, applying 'callback' to each link.
 fn map_links<'e>(
     events: impl Iterator<Item = Event<'e>>,
-    callback: impl Fn(&str, &str) -> (String, String),
+    callback: impl Fn(&str, &str) -> (Option<LinkType>, String, String),
 ) -> impl Iterator<Item = Event<'e>> {
     let mut in_link = false;
-    let mut link_target: Option<CowStr> = None;
+    // holds the origin link target on start event and the rewritten one on end event
+    let mut end_link_target: Option<CowStr> = None;
+    // normally link's type is determined by the type of link tag in the end event,
+    // however in same cases we want to change the link type, for example,
+    // `Shortcut` type doesn't make sense for url links
+    let mut end_link_type: Option<LinkType> = None;
 
     events.map(move |evt| match evt {
         Event::Start(Tag::Link(_, ref target, _)) => {
             in_link = true;
-            link_target = Some(target.clone());
+            end_link_target = Some(target.clone());
             evt
         }
         Event::End(Tag::Link(link_type, target, _)) => {
             in_link = false;
             Event::End(Tag::Link(
-                link_type,
-                link_target.take().unwrap_or(target),
+                end_link_type.unwrap_or(link_type),
+                end_link_target.take().unwrap_or(target),
                 CowStr::Borrowed(""),
             ))
         }
         Event::Text(s) if in_link => {
-            let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
-            link_target = Some(CowStr::Boxed(link_target_s.into()));
+            let (link_type, link_target_s, link_name) =
+                callback(&end_link_target.take().unwrap(), &s);
+            end_link_target = Some(CowStr::Boxed(link_target_s.into()));
+            end_link_type = link_type;
             Event::Text(CowStr::Boxed(link_name.into()))
         }
         Event::Code(s) if in_link => {
-            let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
-            link_target = Some(CowStr::Boxed(link_target_s.into()));
+            let (link_type, link_target_s, link_name) =
+                callback(&end_link_target.take().unwrap(), &s);
+            end_link_target = Some(CowStr::Boxed(link_target_s.into()));
+            end_link_type = link_type;
             Event::Code(CowStr::Boxed(link_name.into()))
         }
         _ => evt,
@@ -468,7 +477,13 @@ fn filename_and_frag_for_def(
             Adt::Union(u) => format!("union.{}.html", u.name(db)),
         },
         Definition::Module(m) => match m.name(db) {
-            Some(name) => format!("{}/index.html", name),
+            // `#[doc(keyword = "...")]` is internal used only by rust compiler
+            Some(name) => match m.attrs(db).by_key("doc").find_string_value_in_tt("keyword") {
+                Some(kw) => {
+                    format!("keyword.{}.html", kw.trim_matches('"'))
+                }
+                None => format!("{}/index.html", name),
+            },
             None => String::from("index.html"),
         },
         Definition::Trait(t) => format!("trait.{}.html", t.name(db)),
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 5908dda8e6..d7a7d2d32a 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -81,9 +81,6 @@ pub mod ext {
     pub fn default_bool() -> ast::Expr {
         expr_from_text("false")
     }
-    pub fn option_none() -> ast::Expr {
-        expr_from_text("None")
-    }
     pub fn empty_block_expr() -> ast::BlockExpr {
         block_expr(None, None)
     }


Test patch:
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index eed50a8562..8a1e95d894 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -1737,34 +1737,34 @@ const test: Foo = Foo {foo: 1, bar: 0}
 }
 
 #[test]
-fn doctest_reorder_impl_items() {
+fn doctest_reorder_impl() {
     check_doc_test(
-        "reorder_impl_items",
+        "reorder_impl",
         r#####"
 trait Foo {
-    type A;
-    const B: u8;
-    fn c();
+    fn a() {}
+    fn b() {}
+    fn c() {}
 }
 
 struct Bar;
 $0impl Foo for Bar {
-    const B: u8 = 17;
+    fn b() {}
     fn c() {}
-    type A = String;
+    fn a() {}
 }
 "#####,
         r#####"
 trait Foo {
-    type A;
-    const B: u8;
-    fn c();
+    fn a() {}
+    fn b() {}
+    fn c() {}
 }
 
 struct Bar;
 impl Foo for Bar {
-    type A = String;
-    const B: u8 = 17;
+    fn a() {}
+    fn b() {}
     fn c() {}
 }
 "#####,
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index d61b5af13a..95420f2ffe 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -3641,6 +3641,40 @@ mod return_keyword {}
     );
 }
 
+#[test]
+fn hover_keyword_doc() {
+    check(
+        r#"
+//- /main.rs crate:main deps:std
+fn foo() {
+    let bar = mov$0e || {};
+}
+//- /libstd.rs crate:std
+#[doc(keyword = "move")]
+/// [closure]
+/// [closures][closure]
+/// [threads]
+///
+/// [closure]: ../book/ch13-01-closures.html
+/// [threads]: ../book/ch16-01-threads.html#using-move-closures-with-threads
+mod move_keyword {}
+"#,
+        expect![[r##"
+            *move*
+
+            ```rust
+            move
+            ```
+
+            ---
+
+            [closure](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html)
+            [closures](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html)
+            [threads](https://doc.rust-lang.org/nightly/book/ch16-01-threads.html#using-move-closures-with-threads)
+        "##]],
+    );
+}
+
 #[test]
 fn hover_keyword_as_primitive() {
     check(