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}
20
21impl<'db> ActiveParameter<'db> {
22 pub fn at_token(sema: &Semantics<'db, RootDatabase>, token: SyntaxToken) -> Option<Self> {
24 let (signature, active_parameter) = callable_for_token(sema, token)?;
25 Self::from_signature_and_active_parameter(sema, signature, active_parameter)
26 }
27
28 pub fn at_arg(
30 sema: &'db Semantics<'db, RootDatabase>,
31 list: ast::ArgList,
32 at: TextSize,
33 ) -> Option<Self> {
34 let (signature, active_parameter) = callable_for_arg_list(sema, list, at)?;
35 Self::from_signature_and_active_parameter(sema, signature, active_parameter)
36 }
37
38 fn from_signature_and_active_parameter(
39 sema: &Semantics<'db, RootDatabase>,
40 signature: hir::Callable<'db>,
41 active_parameter: Option<usize>,
42 ) -> Option<Self> {
43 let idx = active_parameter?;
44 let mut params = signature.params();
45 if idx >= params.len() {
46 cov_mark::hit!(too_many_arguments);
47 return None;
48 }
49 let param = params.swap_remove(idx);
50 Some(ActiveParameter { ty: param.ty().clone(), src: sema.source(param) })
51 }
52
53 pub fn ident(&self) -> Option<ast::Name> {
54 self.src.as_ref().and_then(|param| match param.value.as_ref().right()?.pat()? {
55 ast::Pat::IdentPat(ident) => ident.name(),
56 _ => None,
57 })
58 }
59
60 pub fn attrs(&self) -> Option<AstChildren<ast::Attr>> {
61 self.src.as_ref().and_then(|param| Some(param.value.as_ref().right()?.attrs()))
62 }
63}
64
65pub fn callable_for_token<'db>(
67 sema: &Semantics<'db, RootDatabase>,
68 token: SyntaxToken,
69) -> Option<(hir::Callable<'db>, Option<usize>)> {
70 let offset = token.text_range().start();
71 let parent = token.parent()?;
73 let calling_node = parent
74 .ancestors()
75 .filter_map(ast::CallableExpr::cast)
76 .find(|it| it.arg_list().is_some_and(|it| it.syntax().text_range().contains(offset)))?;
77
78 callable_for_node(sema, &calling_node, offset)
79}
80
81pub fn callable_for_arg_list<'db>(
83 sema: &Semantics<'db, RootDatabase>,
84 arg_list: ast::ArgList,
85 at: TextSize,
86) -> Option<(hir::Callable<'db>, Option<usize>)> {
87 debug_assert!(arg_list.syntax().text_range().contains(at));
88 let callable = arg_list.syntax().parent().and_then(ast::CallableExpr::cast)?;
89 callable_for_node(sema, &callable, at)
90}
91
92pub fn callable_for_node<'db>(
93 sema: &Semantics<'db, RootDatabase>,
94 calling_node: &ast::CallableExpr,
95 offset: TextSize,
96) -> Option<(hir::Callable<'db>, Option<usize>)> {
97 let callable = match calling_node {
98 ast::CallableExpr::Call(call) => sema.resolve_expr_as_callable(&call.expr()?),
99 ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call),
100 }?;
101 let active_param = calling_node.arg_list().map(|arg_list| {
102 arg_list
103 .syntax()
104 .children_with_tokens()
105 .filter_map(into_comma)
106 .take_while(|t| t.text_range().start() <= offset)
107 .count()
108 });
109 Some((callable, active_param))
110}
111
112pub fn generic_def_for_node(
113 sema: &Semantics<'_, RootDatabase>,
114 generic_arg_list: &ast::GenericArgList,
115 token: &SyntaxToken,
116) -> Option<(hir::GenericDef, usize, bool, Option<hir::Variant>)> {
117 let parent = generic_arg_list.syntax().parent()?;
118 let mut variant = None;
119 let def = match_ast! {
120 match parent {
121 ast::PathSegment(ps) => {
122 let res = sema.resolve_path(&ps.parent_path())?;
123 let generic_def: hir::GenericDef = match res {
124 hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
125 hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
126 hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
127 hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
128 hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => {
129 variant = Some(it);
130 it.parent_enum(sema.db).into()
131 },
132 hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
133 | hir::PathResolution::Def(hir::ModuleDef::Const(_))
134 | hir::PathResolution::Def(hir::ModuleDef::Macro(_))
135 | hir::PathResolution::Def(hir::ModuleDef::Module(_))
136 | hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
137 hir::PathResolution::BuiltinAttr(_)
138 | hir::PathResolution::ToolModule(_)
139 | hir::PathResolution::Local(_)
140 | hir::PathResolution::TypeParam(_)
141 | hir::PathResolution::ConstParam(_)
142 | hir::PathResolution::SelfType(_)
143 | hir::PathResolution::DeriveHelper(_) => return None,
144 };
145
146 generic_def
147 },
148 ast::AssocTypeArg(_) => {
149 return None;
151 },
152 ast::MethodCallExpr(mcall) => {
153 let method = sema.resolve_method_call(&mcall)?;
155 method.into()
156 },
157 _ => return None,
158 }
159 };
160
161 let active_param = generic_arg_list
162 .syntax()
163 .children_with_tokens()
164 .filter_map(into_comma)
165 .take_while(|t| t.text_range().start() <= token.text_range().start())
166 .count();
167
168 let first_arg_is_non_lifetime = generic_arg_list
169 .generic_args()
170 .next()
171 .is_some_and(|arg| !matches!(arg, ast::GenericArg::LifetimeArg(_)));
172
173 Some((def, active_param, first_arg_is_non_lifetime, variant))
174}
175
176fn into_comma(it: NodeOrToken<SyntaxNode, SyntaxToken>) -> Option<SyntaxToken> {
177 let token = match it {
178 NodeOrToken::Token(it) => it,
179 NodeOrToken::Node(node) if node.kind() == SyntaxKind::ERROR => node.first_token()?,
180 NodeOrToken::Node(_) => return None,
181 };
182 (token.kind() == T![,]).then_some(token)
183}