algo, ast,
display::{fn_as_proc_macro_label, macro_label},
match_ast, AstNode, Direction,
- SyntaxKind::{CONDITION, LET_STMT},
+ SyntaxKind::{LET_EXPR, LET_STMT},
SyntaxToken, T,
};
}
let parent = token.parent()?;
let famous_defs = FamousDefs(sema, sema.scope(&parent).krate());
- let keyword_mod = if token.kind() == T![fn] && ast::FnPtrType::cast(parent).is_some() {
- // treat fn keyword inside function pointer type as primitive
- format!("prim_{}", token.text())
- } else {
- // std exposes {}_keyword modules with docstrings on the root to document keywords
- format!("{}_keyword", token.text())
- };
+
+ let KeywordHint { description, keyword_mod, actions } = keyword_hints(sema, token, parent);
+
let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
let docs = doc_owner.attrs(sema.db).docs()?;
let markup = process_markup(
sema.db,
Definition::Module(doc_owner),
- &markup(Some(docs.into()), token.text().into(), None)?,
+ &markup(Some(docs.into()), description, None)?,
config,
);
- Some(HoverResult { markup, actions: Default::default() })
+ Some(HoverResult { markup, actions })
}
pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult> {
let name = attr.name(db);
let desc = format!("#[{}]", name);
- let AttributeTemplate { word, list, name_value_str } = attr.template(db);
+ let AttributeTemplate { word, list, name_value_str } = match attr.template(db) {
+ Some(template) => template,
+ None => return Some(Markup::fenced_block(&attr.name(db))),
+ };
let mut docs = "Valid forms are:".to_owned();
if word {
format_to!(docs, "\n - #\\[{}]", name);
let let_kw = if ident
.syntax()
.parent()
- .map_or(false, |p| p.kind() == LET_STMT || p.kind() == CONDITION)
+ .map_or(false, |p| p.kind() == LET_STMT || p.kind() == LET_EXPR)
{
"let "
} else {
};
markup(None, desc, None)
}
+
+struct KeywordHint {
+ description: String,
+ keyword_mod: String,
+ actions: Vec<HoverAction>,
+}
+
+impl KeywordHint {
+ fn new(description: String, keyword_mod: String) -> Self {
+ Self { description, keyword_mod, actions: Vec::default() }
+ }
+}
+
+fn keyword_hints(
+ sema: &Semantics<RootDatabase>,
+ token: &SyntaxToken,
+ parent: syntax::SyntaxNode,
+) -> KeywordHint {
+ match token.kind() {
+ T![await] | T![loop] | T![match] | T![unsafe] | T![as] | T![try] | T![if] | T![else] => {
+ let keyword_mod = format!("{}_keyword", token.text());
+
+ match ast::Expr::cast(parent).and_then(|site| sema.type_of_expr(&site)) {
+ // ignore the unit type ()
+ Some(ty) if !ty.adjusted.as_ref().unwrap_or(&ty.original).is_unit() => {
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &ty.original, &mut push_new_def);
+
+ let ty = ty.adjusted();
+ let description = format!("{}: {}", token.text(), ty.display(sema.db));
+
+ KeywordHint {
+ description,
+ keyword_mod,
+ actions: vec![HoverAction::goto_type_from_targets(sema.db, targets)],
+ }
+ }
+ _ => KeywordHint {
+ description: token.text().to_string(),
+ keyword_mod,
+ actions: Vec::new(),
+ },
+ }
+ }
+
+ T![fn] => {
+ let module = match ast::FnPtrType::cast(parent) {
+ // treat fn keyword inside function pointer type as primitive
+ Some(_) => format!("prim_{}", token.text()),
+ None => format!("{}_keyword", token.text()),
+ };
+ KeywordHint::new(token.text().to_string(), module)
+ }
+
+ _ => KeywordHint::new(token.text().to_string(), format!("{}_keyword", token.text())),
+ }
+}