@@ -239,22 +239,20 @@ pub(super) fn keyword(
239
239
}
240
240
let parent = token. parent ( ) ?;
241
241
let famous_defs = FamousDefs ( sema, sema. scope ( & parent) . krate ( ) ) ;
242
- let keyword_mod = if token. kind ( ) == T ! [ fn ] && ast:: FnPtrType :: cast ( parent) . is_some ( ) {
243
- // treat fn keyword inside function pointer type as primitive
244
- format ! ( "prim_{}" , token. text( ) )
245
- } else {
246
- // std exposes {}_keyword modules with docstrings on the root to document keywords
247
- format ! ( "{}_keyword" , token. text( ) )
248
- } ;
249
- let doc_owner = find_std_module ( & famous_defs, & keyword_mod) ?;
242
+
243
+ // some keywords get fancy type tooltips if they are apart of an expression, which require some extra work
244
+ // panic safety: we just checked that token is a keyword, and we have it's parent in scope, so it must have a parent
245
+ let KeywordHint { description, documentation, actions } = keyword_hints ( sema, token) ;
246
+
247
+ let doc_owner = find_std_module ( & famous_defs, & documentation) ?;
250
248
let docs = doc_owner. attrs ( sema. db ) . docs ( ) ?;
251
249
let markup = process_markup (
252
250
sema. db ,
253
251
Definition :: Module ( doc_owner) ,
254
- & markup ( Some ( docs. into ( ) ) , token . text ( ) . into ( ) , None ) ?,
252
+ & markup ( Some ( docs. into ( ) ) , description , None ) ?,
255
253
config,
256
254
) ;
257
- Some ( HoverResult { markup, actions : Default :: default ( ) } )
255
+ Some ( HoverResult { markup, actions } )
258
256
}
259
257
260
258
pub ( super ) fn try_for_lint ( attr : & ast:: Attr , token : & SyntaxToken ) -> Option < HoverResult > {
@@ -500,3 +498,94 @@ fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
500
498
} ;
501
499
markup ( None , desc, None )
502
500
}
501
+
502
+ struct KeywordHint {
503
+ description : String ,
504
+ documentation : String ,
505
+ actions : Vec < HoverAction > ,
506
+ }
507
+
508
+ impl KeywordHint {
509
+ fn new ( description : String , documentation : String ) -> Self {
510
+ Self { description, documentation, actions : Vec :: default ( ) }
511
+ }
512
+ }
513
+
514
+ /// Panics
515
+ /// ------
516
+ /// `token` is assumed to:
517
+ /// - have a parent, and
518
+ /// - be a keyword
519
+ fn keyword_hints < ' t > ( sema : & Semantics < RootDatabase > , token : & ' t SyntaxToken ) -> KeywordHint {
520
+ let parent = token. parent ( ) . expect ( "token was assumed to have a parent, but had none" ) ;
521
+
522
+ macro_rules! create_hint {
523
+ ( $ty_info: expr, $doc: expr) => { {
524
+ let documentation = $doc;
525
+ match $ty_info {
526
+ Some ( ty) => {
527
+ let mut targets: Vec <hir:: ModuleDef > = Vec :: new( ) ;
528
+ let mut push_new_def = |item: hir:: ModuleDef | {
529
+ if !targets. contains( & item) {
530
+ targets. push( item) ;
531
+ }
532
+ } ;
533
+ walk_and_push_ty( sema. db, & ty. original, & mut push_new_def) ;
534
+
535
+ let ty = ty. adjusted( ) ;
536
+ let description = format!( "{}: {}" , token. text( ) , ty. display( sema. db) ) ;
537
+
538
+ KeywordHint {
539
+ description,
540
+ documentation,
541
+ actions: vec![ HoverAction :: goto_type_from_targets( sema. db, targets) ] ,
542
+ }
543
+ }
544
+ None => KeywordHint {
545
+ description: token. text( ) . to_string( ) ,
546
+ documentation,
547
+ actions: Vec :: new( ) ,
548
+ } ,
549
+ }
550
+ } } ;
551
+ }
552
+
553
+ match token. kind ( ) {
554
+ T ! [ await ] | T ! [ loop ] | T ! [ match ] | T ! [ unsafe ] => {
555
+ let ty = ast:: Expr :: cast ( parent) . and_then ( |site| sema. type_of_expr ( & site) ) ;
556
+ create_hint ! ( ty, format!( "{}_keyword" , token. text( ) ) )
557
+ }
558
+
559
+ T ! [ if ] | T ! [ else] => {
560
+ fn if_has_else ( site : & ast:: IfExpr ) -> bool {
561
+ match site. else_branch ( ) {
562
+ Some ( ast:: ElseBranch :: IfExpr ( inner) ) => if_has_else ( & inner) ,
563
+ Some ( ast:: ElseBranch :: Block ( _) ) => true ,
564
+ None => false ,
565
+ }
566
+ }
567
+
568
+ // only include the type if there is an else branch; it isn't worth annotating
569
+ // an expression that always returns `()`, is it?
570
+ let ty = ast:: IfExpr :: cast ( parent)
571
+ . and_then ( |site| if_has_else ( & site) . then ( || site) )
572
+ . and_then ( |site| sema. type_of_expr ( & ast:: Expr :: IfExpr ( site) ) ) ;
573
+ create_hint ! ( ty, format!( "{}_keyword" , token. text( ) ) )
574
+ }
575
+
576
+ T ! [ fn ] => {
577
+ let module = match ast:: FnPtrType :: cast ( parent) {
578
+ // treat fn keyword inside function pointer type as primitive
579
+ Some ( _) => format ! ( "prim_{}" , token. text( ) ) ,
580
+ None => format ! ( "{}_keyword" , token. text( ) ) ,
581
+ } ;
582
+ KeywordHint :: new ( token. text ( ) . to_string ( ) , module)
583
+ }
584
+
585
+ kind if kind. is_keyword ( ) => {
586
+ KeywordHint :: new ( token. text ( ) . to_string ( ) , format ! ( "{}_keyword" , token. text( ) ) )
587
+ }
588
+
589
+ _ => panic ! ( "{} was assumed to be a keyword, but it wasn't" , token) ,
590
+ }
591
+ }
0 commit comments