diff options
author | Aleksey Kladov <[email protected]> | 2020-02-26 12:04:22 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-02-26 12:04:22 +0000 |
commit | 5c64ad27e041bcdb281c0a751720ceb3a6369d04 (patch) | |
tree | 12d89798f61b276f8bd640db07276a7d4e92b1c2 /crates/ra_hir/src/source_analyzer.rs | |
parent | 04deae3dba7c9b7054f7a1d64e4b93a05aecc132 (diff) | |
parent | c3a4c4429de83450654795534e64e878a774a088 (diff) |
Merge pull request #3222 from matklad/identity
Introduce Semantics API
Diffstat (limited to 'crates/ra_hir/src/source_analyzer.rs')
-rw-r--r-- | crates/ra_hir/src/source_analyzer.rs | 194 |
1 files changed, 73 insertions, 121 deletions
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs index efa3f8a79..bff1ecd14 100644 --- a/crates/ra_hir/src/source_analyzer.rs +++ b/crates/ra_hir/src/source_analyzer.rs | |||
@@ -14,29 +14,27 @@ use hir_def::{ | |||
14 | BodySourceMap, | 14 | BodySourceMap, |
15 | }, | 15 | }, |
16 | expr::{ExprId, PatId}, | 16 | expr::{ExprId, PatId}, |
17 | resolver::{self, resolver_for_scope, Resolver, TypeNs, ValueNs}, | 17 | resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, |
18 | AsMacroCall, DefWithBodyId, TraitId, | 18 | AsMacroCall, DefWithBodyId, |
19 | }; | 19 | }; |
20 | use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile, MacroCallId}; | 20 | use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile}; |
21 | use hir_ty::{InEnvironment, InferenceResult, TraitEnvironment}; | 21 | use hir_ty::{InEnvironment, InferenceResult, TraitEnvironment}; |
22 | use ra_syntax::{ | 22 | use ra_syntax::{ |
23 | ast::{self, AstNode}, | 23 | ast::{self, AstNode}, |
24 | AstPtr, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextUnit, | 24 | AstPtr, SyntaxNode, SyntaxNodePtr, TextRange, TextUnit, |
25 | }; | 25 | }; |
26 | use rustc_hash::FxHashSet; | ||
27 | 26 | ||
28 | use crate::{ | 27 | use crate::{ |
29 | db::HirDatabase, Adt, Const, DefWithBody, EnumVariant, Function, Local, MacroDef, Name, Path, | 28 | db::HirDatabase, Adt, Const, EnumVariant, Function, Local, MacroDef, Name, Path, Static, |
30 | ScopeDef, Static, Struct, Trait, Type, TypeAlias, TypeParam, | 29 | Struct, Trait, Type, TypeAlias, TypeParam, |
31 | }; | 30 | }; |
32 | 31 | ||
33 | /// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of | 32 | /// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of |
34 | /// original source files. It should not be used inside the HIR itself. | 33 | /// original source files. It should not be used inside the HIR itself. |
35 | #[derive(Debug)] | 34 | #[derive(Debug)] |
36 | pub struct SourceAnalyzer { | 35 | pub(crate) struct SourceAnalyzer { |
37 | file_id: HirFileId, | 36 | file_id: HirFileId, |
38 | resolver: Resolver, | 37 | pub(crate) resolver: Resolver, |
39 | body_owner: Option<DefWithBody>, | ||
40 | body_source_map: Option<Arc<BodySourceMap>>, | 38 | body_source_map: Option<Arc<BodySourceMap>>, |
41 | infer: Option<Arc<InferenceResult>>, | 39 | infer: Option<Arc<InferenceResult>>, |
42 | scopes: Option<Arc<ExprScopes>>, | 40 | scopes: Option<Arc<ExprScopes>>, |
@@ -77,35 +75,7 @@ pub struct ReferenceDescriptor { | |||
77 | pub name: String, | 75 | pub name: String, |
78 | } | 76 | } |
79 | 77 | ||
80 | #[derive(Debug)] | ||
81 | pub struct Expansion { | ||
82 | macro_call_id: MacroCallId, | ||
83 | } | ||
84 | |||
85 | impl Expansion { | ||
86 | pub fn map_token_down( | ||
87 | &self, | ||
88 | db: &impl HirDatabase, | ||
89 | token: InFile<&SyntaxToken>, | ||
90 | ) -> Option<InFile<SyntaxToken>> { | ||
91 | let exp_info = self.file_id().expansion_info(db)?; | ||
92 | exp_info.map_token_down(token) | ||
93 | } | ||
94 | |||
95 | pub fn file_id(&self) -> HirFileId { | ||
96 | self.macro_call_id.as_file() | ||
97 | } | ||
98 | } | ||
99 | |||
100 | impl SourceAnalyzer { | 78 | impl SourceAnalyzer { |
101 | pub fn new( | ||
102 | db: &impl HirDatabase, | ||
103 | node: InFile<&SyntaxNode>, | ||
104 | offset: Option<TextUnit>, | ||
105 | ) -> SourceAnalyzer { | ||
106 | crate::source_binder::SourceBinder::new(db).analyze(node, offset) | ||
107 | } | ||
108 | |||
109 | pub(crate) fn new_for_body( | 79 | pub(crate) fn new_for_body( |
110 | db: &impl HirDatabase, | 80 | db: &impl HirDatabase, |
111 | def: DefWithBodyId, | 81 | def: DefWithBodyId, |
@@ -121,7 +91,6 @@ impl SourceAnalyzer { | |||
121 | let resolver = resolver_for_scope(db, def, scope); | 91 | let resolver = resolver_for_scope(db, def, scope); |
122 | SourceAnalyzer { | 92 | SourceAnalyzer { |
123 | resolver, | 93 | resolver, |
124 | body_owner: Some(def.into()), | ||
125 | body_source_map: Some(source_map), | 94 | body_source_map: Some(source_map), |
126 | infer: Some(db.infer(def)), | 95 | infer: Some(db.infer(def)), |
127 | scopes: Some(scopes), | 96 | scopes: Some(scopes), |
@@ -135,7 +104,6 @@ impl SourceAnalyzer { | |||
135 | ) -> SourceAnalyzer { | 104 | ) -> SourceAnalyzer { |
136 | SourceAnalyzer { | 105 | SourceAnalyzer { |
137 | resolver, | 106 | resolver, |
138 | body_owner: None, | ||
139 | body_source_map: None, | 107 | body_source_map: None, |
140 | infer: None, | 108 | infer: None, |
141 | scopes: None, | 109 | scopes: None, |
@@ -143,10 +111,6 @@ impl SourceAnalyzer { | |||
143 | } | 111 | } |
144 | } | 112 | } |
145 | 113 | ||
146 | pub fn module(&self) -> Option<crate::code_model::Module> { | ||
147 | Some(crate::code_model::Module { id: self.resolver.module()? }) | ||
148 | } | ||
149 | |||
150 | fn expr_id(&self, expr: &ast::Expr) -> Option<ExprId> { | 114 | fn expr_id(&self, expr: &ast::Expr) -> Option<ExprId> { |
151 | let src = InFile { file_id: self.file_id, value: expr }; | 115 | let src = InFile { file_id: self.file_id, value: expr }; |
152 | self.body_source_map.as_ref()?.node_expr(src) | 116 | self.body_source_map.as_ref()?.node_expr(src) |
@@ -180,7 +144,7 @@ impl SourceAnalyzer { | |||
180 | TraitEnvironment::lower(db, &self.resolver) | 144 | TraitEnvironment::lower(db, &self.resolver) |
181 | } | 145 | } |
182 | 146 | ||
183 | pub fn type_of(&self, db: &impl HirDatabase, expr: &ast::Expr) -> Option<Type> { | 147 | pub(crate) fn type_of(&self, db: &impl HirDatabase, expr: &ast::Expr) -> Option<Type> { |
184 | let expr_id = if let Some(expr) = self.expand_expr(db, InFile::new(self.file_id, expr)) { | 148 | let expr_id = if let Some(expr) = self.expand_expr(db, InFile::new(self.file_id, expr)) { |
185 | self.body_source_map.as_ref()?.node_expr(expr.as_ref())? | 149 | self.body_source_map.as_ref()?.node_expr(expr.as_ref())? |
186 | } else { | 150 | } else { |
@@ -192,24 +156,27 @@ impl SourceAnalyzer { | |||
192 | Some(Type { krate: self.resolver.krate()?, ty: InEnvironment { value: ty, environment } }) | 156 | Some(Type { krate: self.resolver.krate()?, ty: InEnvironment { value: ty, environment } }) |
193 | } | 157 | } |
194 | 158 | ||
195 | pub fn type_of_pat(&self, db: &impl HirDatabase, pat: &ast::Pat) -> Option<Type> { | 159 | pub(crate) fn type_of_pat(&self, db: &impl HirDatabase, pat: &ast::Pat) -> Option<Type> { |
196 | let pat_id = self.pat_id(pat)?; | 160 | let pat_id = self.pat_id(pat)?; |
197 | let ty = self.infer.as_ref()?[pat_id].clone(); | 161 | let ty = self.infer.as_ref()?[pat_id].clone(); |
198 | let environment = self.trait_env(db); | 162 | let environment = self.trait_env(db); |
199 | Some(Type { krate: self.resolver.krate()?, ty: InEnvironment { value: ty, environment } }) | 163 | Some(Type { krate: self.resolver.krate()?, ty: InEnvironment { value: ty, environment } }) |
200 | } | 164 | } |
201 | 165 | ||
202 | pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> { | 166 | pub(crate) fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> { |
203 | let expr_id = self.expr_id(&call.clone().into())?; | 167 | let expr_id = self.expr_id(&call.clone().into())?; |
204 | self.infer.as_ref()?.method_resolution(expr_id).map(Function::from) | 168 | self.infer.as_ref()?.method_resolution(expr_id).map(Function::from) |
205 | } | 169 | } |
206 | 170 | ||
207 | pub fn resolve_field(&self, field: &ast::FieldExpr) -> Option<crate::StructField> { | 171 | pub(crate) fn resolve_field(&self, field: &ast::FieldExpr) -> Option<crate::StructField> { |
208 | let expr_id = self.expr_id(&field.clone().into())?; | 172 | let expr_id = self.expr_id(&field.clone().into())?; |
209 | self.infer.as_ref()?.field_resolution(expr_id).map(|it| it.into()) | 173 | self.infer.as_ref()?.field_resolution(expr_id).map(|it| it.into()) |
210 | } | 174 | } |
211 | 175 | ||
212 | pub fn resolve_record_field(&self, field: &ast::RecordField) -> Option<crate::StructField> { | 176 | pub(crate) fn resolve_record_field( |
177 | &self, | ||
178 | field: &ast::RecordField, | ||
179 | ) -> Option<crate::StructField> { | ||
213 | let expr_id = match field.expr() { | 180 | let expr_id = match field.expr() { |
214 | Some(it) => self.expr_id(&it)?, | 181 | Some(it) => self.expr_id(&it)?, |
215 | None => { | 182 | None => { |
@@ -220,17 +187,23 @@ impl SourceAnalyzer { | |||
220 | self.infer.as_ref()?.record_field_resolution(expr_id).map(|it| it.into()) | 187 | self.infer.as_ref()?.record_field_resolution(expr_id).map(|it| it.into()) |
221 | } | 188 | } |
222 | 189 | ||
223 | pub fn resolve_record_literal(&self, record_lit: &ast::RecordLit) -> Option<crate::VariantDef> { | 190 | pub(crate) fn resolve_record_literal( |
191 | &self, | ||
192 | record_lit: &ast::RecordLit, | ||
193 | ) -> Option<crate::VariantDef> { | ||
224 | let expr_id = self.expr_id(&record_lit.clone().into())?; | 194 | let expr_id = self.expr_id(&record_lit.clone().into())?; |
225 | self.infer.as_ref()?.variant_resolution_for_expr(expr_id).map(|it| it.into()) | 195 | self.infer.as_ref()?.variant_resolution_for_expr(expr_id).map(|it| it.into()) |
226 | } | 196 | } |
227 | 197 | ||
228 | pub fn resolve_record_pattern(&self, record_pat: &ast::RecordPat) -> Option<crate::VariantDef> { | 198 | pub(crate) fn resolve_record_pattern( |
199 | &self, | ||
200 | record_pat: &ast::RecordPat, | ||
201 | ) -> Option<crate::VariantDef> { | ||
229 | let pat_id = self.pat_id(&record_pat.clone().into())?; | 202 | let pat_id = self.pat_id(&record_pat.clone().into())?; |
230 | self.infer.as_ref()?.variant_resolution_for_pat(pat_id).map(|it| it.into()) | 203 | self.infer.as_ref()?.variant_resolution_for_pat(pat_id).map(|it| it.into()) |
231 | } | 204 | } |
232 | 205 | ||
233 | pub fn resolve_macro_call( | 206 | pub(crate) fn resolve_macro_call( |
234 | &self, | 207 | &self, |
235 | db: &impl HirDatabase, | 208 | db: &impl HirDatabase, |
236 | macro_call: InFile<&ast::MacroCall>, | 209 | macro_call: InFile<&ast::MacroCall>, |
@@ -240,52 +213,11 @@ impl SourceAnalyzer { | |||
240 | self.resolver.resolve_path_as_macro(db, path.mod_path()).map(|it| it.into()) | 213 | self.resolver.resolve_path_as_macro(db, path.mod_path()).map(|it| it.into()) |
241 | } | 214 | } |
242 | 215 | ||
243 | pub fn resolve_hir_path( | 216 | pub(crate) fn resolve_path( |
244 | &self, | 217 | &self, |
245 | db: &impl HirDatabase, | 218 | db: &impl HirDatabase, |
246 | path: &crate::Path, | 219 | path: &ast::Path, |
247 | ) -> Option<PathResolution> { | 220 | ) -> Option<PathResolution> { |
248 | let types = | ||
249 | self.resolver.resolve_path_in_type_ns_fully(db, path.mod_path()).map(|ty| match ty { | ||
250 | TypeNs::SelfType(it) => PathResolution::SelfType(it.into()), | ||
251 | TypeNs::GenericParam(id) => PathResolution::TypeParam(TypeParam { id }), | ||
252 | TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => { | ||
253 | PathResolution::Def(Adt::from(it).into()) | ||
254 | } | ||
255 | TypeNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), | ||
256 | TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()), | ||
257 | TypeNs::BuiltinType(it) => PathResolution::Def(it.into()), | ||
258 | TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), | ||
259 | }); | ||
260 | let values = | ||
261 | self.resolver.resolve_path_in_value_ns_fully(db, path.mod_path()).and_then(|val| { | ||
262 | let res = match val { | ||
263 | ValueNs::LocalBinding(pat_id) => { | ||
264 | let var = Local { parent: self.body_owner?, pat_id }; | ||
265 | PathResolution::Local(var) | ||
266 | } | ||
267 | ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()), | ||
268 | ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()), | ||
269 | ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()), | ||
270 | ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()), | ||
271 | ValueNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), | ||
272 | }; | ||
273 | Some(res) | ||
274 | }); | ||
275 | |||
276 | let items = self | ||
277 | .resolver | ||
278 | .resolve_module_path_in_items(db, path.mod_path()) | ||
279 | .take_types() | ||
280 | .map(|it| PathResolution::Def(it.into())); | ||
281 | types.or(values).or(items).or_else(|| { | ||
282 | self.resolver | ||
283 | .resolve_path_as_macro(db, path.mod_path()) | ||
284 | .map(|def| PathResolution::Macro(def.into())) | ||
285 | }) | ||
286 | } | ||
287 | |||
288 | pub fn resolve_path(&self, db: &impl HirDatabase, path: &ast::Path) -> Option<PathResolution> { | ||
289 | if let Some(path_expr) = path.syntax().parent().and_then(ast::PathExpr::cast) { | 221 | if let Some(path_expr) = path.syntax().parent().and_then(ast::PathExpr::cast) { |
290 | let expr_id = self.expr_id(&path_expr.into())?; | 222 | let expr_id = self.expr_id(&path_expr.into())?; |
291 | if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) { | 223 | if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) { |
@@ -300,7 +232,7 @@ impl SourceAnalyzer { | |||
300 | } | 232 | } |
301 | // This must be a normal source file rather than macro file. | 233 | // This must be a normal source file rather than macro file. |
302 | let hir_path = crate::Path::from_ast(path.clone())?; | 234 | let hir_path = crate::Path::from_ast(path.clone())?; |
303 | self.resolve_hir_path(db, &hir_path) | 235 | resolve_hir_path(db, &self.resolver, &hir_path) |
304 | } | 236 | } |
305 | 237 | ||
306 | fn resolve_local_name(&self, name_ref: &ast::NameRef) -> Option<ScopeEntryWithSyntax> { | 238 | fn resolve_local_name(&self, name_ref: &ast::NameRef) -> Option<ScopeEntryWithSyntax> { |
@@ -315,25 +247,9 @@ impl SourceAnalyzer { | |||
315 | }) | 247 | }) |
316 | } | 248 | } |
317 | 249 | ||
318 | pub fn process_all_names(&self, db: &impl HirDatabase, f: &mut dyn FnMut(Name, ScopeDef)) { | ||
319 | self.resolver.process_all_names(db, &mut |name, def| { | ||
320 | let def = match def { | ||
321 | resolver::ScopeDef::PerNs(it) => it.into(), | ||
322 | resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()), | ||
323 | resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()), | ||
324 | resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(TypeParam { id }), | ||
325 | resolver::ScopeDef::Local(pat_id) => { | ||
326 | let parent = self.resolver.body_owner().unwrap().into(); | ||
327 | ScopeDef::Local(Local { parent, pat_id }) | ||
328 | } | ||
329 | }; | ||
330 | f(name, def) | ||
331 | }) | ||
332 | } | ||
333 | |||
334 | // FIXME: we only use this in `inline_local_variable` assist, ideally, we | 250 | // FIXME: we only use this in `inline_local_variable` assist, ideally, we |
335 | // should switch to general reference search infra there. | 251 | // should switch to general reference search infra there. |
336 | pub fn find_all_refs(&self, pat: &ast::BindPat) -> Vec<ReferenceDescriptor> { | 252 | pub(crate) fn find_all_refs(&self, pat: &ast::BindPat) -> Vec<ReferenceDescriptor> { |
337 | let fn_def = pat.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); | 253 | let fn_def = pat.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); |
338 | let ptr = Either::Left(AstPtr::new(&ast::Pat::from(pat.clone()))); | 254 | let ptr = Either::Left(AstPtr::new(&ast::Pat::from(pat.clone()))); |
339 | fn_def | 255 | fn_def |
@@ -351,19 +267,14 @@ impl SourceAnalyzer { | |||
351 | .collect() | 267 | .collect() |
352 | } | 268 | } |
353 | 269 | ||
354 | /// Note: `FxHashSet<TraitId>` should be treated as an opaque type, passed into `Type | 270 | pub(crate) fn expand( |
355 | pub fn traits_in_scope(&self, db: &impl HirDatabase) -> FxHashSet<TraitId> { | ||
356 | self.resolver.traits_in_scope(db) | ||
357 | } | ||
358 | |||
359 | pub fn expand( | ||
360 | &self, | 271 | &self, |
361 | db: &impl HirDatabase, | 272 | db: &impl HirDatabase, |
362 | macro_call: InFile<&ast::MacroCall>, | 273 | macro_call: InFile<&ast::MacroCall>, |
363 | ) -> Option<Expansion> { | 274 | ) -> Option<HirFileId> { |
364 | let macro_call_id = | 275 | let macro_call_id = |
365 | macro_call.as_call_id(db, |path| self.resolver.resolve_path_as_macro(db, &path))?; | 276 | macro_call.as_call_id(db, |path| self.resolver.resolve_path_as_macro(db, &path))?; |
366 | Some(Expansion { macro_call_id }) | 277 | Some(macro_call_id.as_file()) |
367 | } | 278 | } |
368 | } | 279 | } |
369 | 280 | ||
@@ -409,6 +320,47 @@ fn scope_for_offset( | |||
409 | }) | 320 | }) |
410 | } | 321 | } |
411 | 322 | ||
323 | pub(crate) fn resolve_hir_path( | ||
324 | db: &impl HirDatabase, | ||
325 | resolver: &Resolver, | ||
326 | path: &crate::Path, | ||
327 | ) -> Option<PathResolution> { | ||
328 | let types = resolver.resolve_path_in_type_ns_fully(db, path.mod_path()).map(|ty| match ty { | ||
329 | TypeNs::SelfType(it) => PathResolution::SelfType(it.into()), | ||
330 | TypeNs::GenericParam(id) => PathResolution::TypeParam(TypeParam { id }), | ||
331 | TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => PathResolution::Def(Adt::from(it).into()), | ||
332 | TypeNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), | ||
333 | TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()), | ||
334 | TypeNs::BuiltinType(it) => PathResolution::Def(it.into()), | ||
335 | TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), | ||
336 | }); | ||
337 | let body_owner = resolver.body_owner(); | ||
338 | let values = resolver.resolve_path_in_value_ns_fully(db, path.mod_path()).and_then(|val| { | ||
339 | let res = match val { | ||
340 | ValueNs::LocalBinding(pat_id) => { | ||
341 | let var = Local { parent: body_owner?.into(), pat_id }; | ||
342 | PathResolution::Local(var) | ||
343 | } | ||
344 | ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()), | ||
345 | ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()), | ||
346 | ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()), | ||
347 | ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()), | ||
348 | ValueNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), | ||
349 | }; | ||
350 | Some(res) | ||
351 | }); | ||
352 | |||
353 | let items = resolver | ||
354 | .resolve_module_path_in_items(db, path.mod_path()) | ||
355 | .take_types() | ||
356 | .map(|it| PathResolution::Def(it.into())); | ||
357 | types.or(values).or(items).or_else(|| { | ||
358 | resolver | ||
359 | .resolve_path_as_macro(db, path.mod_path()) | ||
360 | .map(|def| PathResolution::Macro(def.into())) | ||
361 | }) | ||
362 | } | ||
363 | |||
412 | // XXX: during completion, cursor might be outside of any particular | 364 | // XXX: during completion, cursor might be outside of any particular |
413 | // expression. Try to figure out the correct scope... | 365 | // expression. Try to figure out the correct scope... |
414 | fn adjust( | 366 | fn adjust( |