Skip to main content

ide_db/
active_parameter.rs

1//! This module provides functionality for querying callable information about a token.
2
3use either::Either;
4use hir::{InFile, Semantics, Type};
5use parser::T;
6use span::TextSize;
7use syntax::{
8    AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken,
9    ast::{self, AstChildren, HasArgList, HasAttrs, HasName},
10    match_ast,
11};
12
13use crate::RootDatabase;
14
15#[derive(Debug)]
16pub struct ActiveParameter<'db> {
17    pub ty: Type<'db>,
18    pub src: Option<InFile<Either<ast::SelfParam, ast::Param>>>,
19    pub original: Option<hir::Param<'db>>,
20}
21
22impl<'db> ActiveParameter<'db> {
23    /// Returns information about the call argument this token is part of.
24    pub fn at_token(sema: &Semantics<'db, RootDatabase>, token: SyntaxToken) -> Option<Self> {
25        let (signature, active_parameter) = callable_for_token(sema, token)?;
26        Self::from_signature_and_active_parameter(sema, signature, active_parameter)
27    }
28
29    /// Returns information about the call argument this token is part of.
30    pub fn at_arg(
31        sema: &Semantics<'db, RootDatabase>,
32        list: ast::ArgList,
33        at: TextSize,
34    ) -> Option<Self> {
35        let (signature, active_parameter) = callable_for_arg_list(sema, list, at)?;
36        Self::from_signature_and_active_parameter(sema, signature, active_parameter)
37    }
38
39    fn from_signature_and_active_parameter(
40        sema: &Semantics<'db, RootDatabase>,
41        signature: hir::Callable<'db>,
42        active_parameter: Option<usize>,
43    ) -> Option<Self> {
44        let idx = active_parameter?;
45        let mut params = signature.params();
46        if idx >= params.len() {
47            cov_mark::hit!(too_many_arguments);
48            return None;
49        }
50        let original = signature.as_function().map(|func| {
51            let mut assoc_params = func.assoc_fn_params(sema.db);
52            let assoc_idx = assoc_params.len() - params.len() + idx;
53            assoc_params.swap_remove(assoc_idx)
54        });
55        let param = params.swap_remove(idx);
56        Some(ActiveParameter { ty: param.ty().clone(), src: sema.source(param), original })
57    }
58
59    pub fn ident(&self) -> Option<ast::Name> {
60        self.src.as_ref().and_then(|param| match param.value.as_ref().right()?.pat()? {
61            ast::Pat::IdentPat(ident) => ident.name(),
62            _ => None,
63        })
64    }
65
66    pub fn attrs(&self) -> Option<AstChildren<ast::Attr>> {
67        self.src.as_ref().and_then(|param| Some(param.value.as_ref().right()?.attrs()))
68    }
69}
70
71/// Returns a [`hir::Callable`] this token is a part of and its argument index of said callable.
72pub fn callable_for_token<'db>(
73    sema: &Semantics<'db, RootDatabase>,
74    token: SyntaxToken,
75) -> Option<(hir::Callable<'db>, Option<usize>)> {
76    let offset = token.text_range().start();
77    // Find the calling expression and its NameRef
78    let parent = token.parent()?;
79    let calling_node = parent
80        .ancestors()
81        .filter_map(ast::CallableExpr::cast)
82        .find(|it| it.arg_list().is_some_and(|it| it.syntax().text_range().contains(offset)))?;
83
84    callable_for_node(sema, &calling_node, offset)
85}
86
87/// Returns a [`hir::Callable`] this token is a part of and its argument index of said callable.
88pub fn callable_for_arg_list<'db>(
89    sema: &Semantics<'db, RootDatabase>,
90    arg_list: ast::ArgList,
91    at: TextSize,
92) -> Option<(hir::Callable<'db>, Option<usize>)> {
93    debug_assert!(arg_list.syntax().text_range().contains(at));
94    let callable = arg_list.syntax().parent().and_then(ast::CallableExpr::cast)?;
95    callable_for_node(sema, &callable, at)
96}
97
98pub fn callable_for_node<'db>(
99    sema: &Semantics<'db, RootDatabase>,
100    calling_node: &ast::CallableExpr,
101    offset: TextSize,
102) -> Option<(hir::Callable<'db>, Option<usize>)> {
103    let callable = match calling_node {
104        ast::CallableExpr::Call(call) => sema.resolve_expr_as_callable(&call.expr()?),
105        ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call),
106    }?;
107    let active_param = calling_node.arg_list().map(|arg_list| {
108        arg_list
109            .syntax()
110            .children_with_tokens()
111            .filter_map(into_comma)
112            .take_while(|t| t.text_range().start() <= offset)
113            .count()
114    });
115    Some((callable, active_param))
116}
117
118pub fn generic_def_for_node(
119    sema: &Semantics<'_, RootDatabase>,
120    generic_arg_list: &ast::GenericArgList,
121    token: &SyntaxToken,
122) -> Option<(hir::GenericDef, usize, bool, Option<hir::EnumVariant>)> {
123    let parent = generic_arg_list.syntax().parent()?;
124    let mut variant = None;
125    let def = match_ast! {
126        match parent {
127            ast::PathSegment(ps) => {
128                let res = sema.resolve_path(&ps.parent_path())?;
129                let generic_def: hir::GenericDef = match res {
130                    hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
131                    hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
132                    hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
133                    hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
134                    hir::PathResolution::Def(hir::ModuleDef::EnumVariant(it)) => {
135                        variant = Some(it);
136                        it.parent_enum(sema.db).into()
137                    },
138                    hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
139                    | hir::PathResolution::Def(hir::ModuleDef::Const(_))
140                    | hir::PathResolution::Def(hir::ModuleDef::Macro(_))
141                    | hir::PathResolution::Def(hir::ModuleDef::Module(_))
142                    | hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
143                    hir::PathResolution::BuiltinAttr(_)
144                    | hir::PathResolution::ToolModule(_)
145                    | hir::PathResolution::Local(_)
146                    | hir::PathResolution::TypeParam(_)
147                    | hir::PathResolution::ConstParam(_)
148                    | hir::PathResolution::SelfType(_)
149                    | hir::PathResolution::DeriveHelper(_) => return None,
150                };
151
152                generic_def
153            },
154            ast::AssocTypeArg(_) => {
155                // FIXME: We don't record the resolutions for this anywhere atm
156                return None;
157            },
158            ast::MethodCallExpr(mcall) => {
159                // recv.method::<$0>()
160                let method = sema.resolve_method_call(&mcall)?;
161                method.into()
162            },
163            _ => return None,
164        }
165    };
166
167    let active_param = generic_arg_list
168        .syntax()
169        .children_with_tokens()
170        .filter_map(into_comma)
171        .take_while(|t| t.text_range().start() <= token.text_range().start())
172        .count();
173
174    let first_arg_is_non_lifetime = generic_arg_list
175        .generic_args()
176        .next()
177        .is_some_and(|arg| !matches!(arg, ast::GenericArg::LifetimeArg(_)));
178
179    Some((def, active_param, first_arg_is_non_lifetime, variant))
180}
181
182fn into_comma(it: NodeOrToken<SyntaxNode, SyntaxToken>) -> Option<SyntaxToken> {
183    let token = match it {
184        NodeOrToken::Token(it) => it,
185        NodeOrToken::Node(node) if node.kind() == SyntaxKind::ERROR => node.first_token()?,
186        NodeOrToken::Node(_) => return None,
187    };
188    (token.kind() == T![,]).then_some(token)
189}