1use 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 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 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
71pub 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 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
87pub 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 return None;
157 },
158 ast::MethodCallExpr(mcall) => {
159 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}