aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_hir/src/code_model.rs31
-rw-r--r--crates/ra_hir/src/semantics.rs28
-rw-r--r--crates/ra_hir/src/source_analyzer.rs90
-rw-r--r--crates/ra_hir_ty/src/expr.rs158
-rw-r--r--crates/ra_ide/src/completion/complete_record.rs59
5 files changed, 198 insertions, 168 deletions
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index c6f3bdb8e..9baebf643 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -1027,8 +1027,16 @@ impl Type {
1027 ty: Ty, 1027 ty: Ty,
1028 ) -> Option<Type> { 1028 ) -> Option<Type> {
1029 let krate = resolver.krate()?; 1029 let krate = resolver.krate()?;
1030 Some(Type::new_with_resolver_inner(db, krate, resolver, ty))
1031 }
1032 pub(crate) fn new_with_resolver_inner(
1033 db: &dyn HirDatabase,
1034 krate: CrateId,
1035 resolver: &Resolver,
1036 ty: Ty,
1037 ) -> Type {
1030 let environment = TraitEnvironment::lower(db, &resolver); 1038 let environment = TraitEnvironment::lower(db, &resolver);
1031 Some(Type { krate, ty: InEnvironment { value: ty, environment } }) 1039 Type { krate, ty: InEnvironment { value: ty, environment } }
1032 } 1040 }
1033 1041
1034 fn new(db: &dyn HirDatabase, krate: CrateId, lexical_env: impl HasResolver, ty: Ty) -> Type { 1042 fn new(db: &dyn HirDatabase, krate: CrateId, lexical_env: impl HasResolver, ty: Ty) -> Type {
@@ -1152,27 +1160,6 @@ impl Type {
1152 res 1160 res
1153 } 1161 }
1154 1162
1155 pub fn variant_fields(
1156 &self,
1157 db: &dyn HirDatabase,
1158 def: VariantDef,
1159 ) -> Vec<(StructField, Type)> {
1160 // FIXME: check that ty and def match
1161 match &self.ty.value {
1162 Ty::Apply(a_ty) => {
1163 let field_types = db.field_types(def.into());
1164 def.fields(db)
1165 .into_iter()
1166 .map(|it| {
1167 let ty = field_types[it.id].clone().subst(&a_ty.parameters);
1168 (it, self.derived(ty))
1169 })
1170 .collect()
1171 }
1172 _ => Vec::new(),
1173 }
1174 }
1175
1176 pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a { 1163 pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a {
1177 // There should be no inference vars in types passed here 1164 // There should be no inference vars in types passed here
1178 // FIXME check that? 1165 // FIXME check that?
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs
index 2ad231d36..2707e422d 100644
--- a/crates/ra_hir/src/semantics.rs
+++ b/crates/ra_hir/src/semantics.rs
@@ -23,7 +23,7 @@ use crate::{
23 semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, 23 semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
24 source_analyzer::{resolve_hir_path, SourceAnalyzer}, 24 source_analyzer::{resolve_hir_path, SourceAnalyzer},
25 AssocItem, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name, 25 AssocItem, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name,
26 Origin, Path, ScopeDef, StructField, Trait, Type, TypeParam, VariantDef, 26 Origin, Path, ScopeDef, StructField, Trait, Type, TypeParam,
27}; 27};
28 28
29#[derive(Debug, Clone, PartialEq, Eq)] 29#[derive(Debug, Clone, PartialEq, Eq)]
@@ -187,14 +187,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
187 self.analyze(field.syntax()).resolve_record_field(self.db, field) 187 self.analyze(field.syntax()).resolve_record_field(self.db, field)
188 } 188 }
189 189
190 pub fn resolve_record_literal(&self, record_lit: &ast::RecordLit) -> Option<VariantDef> {
191 self.analyze(record_lit.syntax()).resolve_record_literal(self.db, record_lit)
192 }
193
194 pub fn resolve_record_pattern(&self, record_pat: &ast::RecordPat) -> Option<VariantDef> {
195 self.analyze(record_pat.syntax()).resolve_record_pattern(record_pat)
196 }
197
198 pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> { 190 pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
199 let sa = self.analyze(macro_call.syntax()); 191 let sa = self.analyze(macro_call.syntax());
200 let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call); 192 let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call);
@@ -212,6 +204,24 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
212 // FIXME: use this instead? 204 // FIXME: use this instead?
213 // pub fn resolve_name_ref(&self, name_ref: &ast::NameRef) -> Option<???>; 205 // pub fn resolve_name_ref(&self, name_ref: &ast::NameRef) -> Option<???>;
214 206
207 pub fn record_literal_missing_fields(
208 &self,
209 literal: &ast::RecordLit,
210 ) -> Vec<(StructField, Type)> {
211 self.analyze(literal.syntax())
212 .record_literal_missing_fields(self.db, literal)
213 .unwrap_or_default()
214 }
215
216 pub fn record_pattern_missing_fields(
217 &self,
218 pattern: &ast::RecordPat,
219 ) -> Vec<(StructField, Type)> {
220 self.analyze(pattern.syntax())
221 .record_pattern_missing_fields(self.db, pattern)
222 .unwrap_or_default()
223 }
224
215 pub fn to_def<T: ToDef>(&self, src: &T) -> Option<T::Def> { 225 pub fn to_def<T: ToDef>(&self, src: &T) -> Option<T::Def> {
216 let src = self.find_file(src.syntax().clone()).with_value(src).cloned(); 226 let src = self.find_file(src.syntax().clone()).with_value(src).cloned();
217 T::to_def(self, src) 227 T::to_def(self, src)
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs
index 815ca158c..45631f8fd 100644
--- a/crates/ra_hir/src/source_analyzer.rs
+++ b/crates/ra_hir/src/source_analyzer.rs
@@ -14,10 +14,13 @@ use hir_def::{
14 }, 14 },
15 expr::{ExprId, Pat, PatId}, 15 expr::{ExprId, Pat, PatId},
16 resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, 16 resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
17 AsMacroCall, DefWithBodyId, 17 AsMacroCall, DefWithBodyId, LocalStructFieldId, StructFieldId, VariantId,
18}; 18};
19use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile}; 19use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile};
20use hir_ty::InferenceResult; 20use hir_ty::{
21 expr::{record_literal_missing_fields, record_pattern_missing_fields},
22 InferenceResult, Substs, Ty,
23};
21use ra_syntax::{ 24use ra_syntax::{
22 ast::{self, AstNode}, 25 ast::{self, AstNode},
23 SyntaxNode, SyntaxNodePtr, TextUnit, 26 SyntaxNode, SyntaxNodePtr, TextUnit,
@@ -25,8 +28,10 @@ use ra_syntax::{
25 28
26use crate::{ 29use crate::{
27 db::HirDatabase, semantics::PathResolution, Adt, Const, EnumVariant, Function, Local, MacroDef, 30 db::HirDatabase, semantics::PathResolution, Adt, Const, EnumVariant, Function, Local, MacroDef,
28 ModPath, ModuleDef, Path, PathKind, Static, Struct, Trait, Type, TypeAlias, TypeParam, 31 ModPath, ModuleDef, Path, PathKind, Static, Struct, StructField, Trait, Type, TypeAlias,
32 TypeParam,
29}; 33};
34use ra_db::CrateId;
30 35
31/// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of 36/// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of
32/// original source files. It should not be used inside the HIR itself. 37/// original source files. It should not be used inside the HIR itself.
@@ -164,23 +169,6 @@ impl SourceAnalyzer {
164 Some((struct_field.into(), local)) 169 Some((struct_field.into(), local))
165 } 170 }
166 171
167 pub(crate) fn resolve_record_literal(
168 &self,
169 db: &dyn HirDatabase,
170 record_lit: &ast::RecordLit,
171 ) -> Option<crate::VariantDef> {
172 let expr_id = self.expr_id(db, &record_lit.clone().into())?;
173 self.infer.as_ref()?.variant_resolution_for_expr(expr_id).map(|it| it.into())
174 }
175
176 pub(crate) fn resolve_record_pattern(
177 &self,
178 record_pat: &ast::RecordPat,
179 ) -> Option<crate::VariantDef> {
180 let pat_id = self.pat_id(&record_pat.clone().into())?;
181 self.infer.as_ref()?.variant_resolution_for_pat(pat_id).map(|it| it.into())
182 }
183
184 pub(crate) fn resolve_macro_call( 172 pub(crate) fn resolve_macro_call(
185 &self, 173 &self,
186 db: &dyn HirDatabase, 174 db: &dyn HirDatabase,
@@ -231,6 +219,68 @@ impl SourceAnalyzer {
231 resolve_hir_path(db, &self.resolver, &hir_path) 219 resolve_hir_path(db, &self.resolver, &hir_path)
232 } 220 }
233 221
222 pub(crate) fn record_literal_missing_fields(
223 &self,
224 db: &dyn HirDatabase,
225 literal: &ast::RecordLit,
226 ) -> Option<Vec<(StructField, Type)>> {
227 let krate = self.resolver.krate()?;
228 let body = self.body.as_ref()?;
229 let infer = self.infer.as_ref()?;
230
231 let expr_id = self.expr_id(db, &literal.clone().into())?;
232 let substs = match &infer.type_of_expr[expr_id] {
233 Ty::Apply(a_ty) => &a_ty.parameters,
234 _ => return None,
235 };
236
237 let (variant, missing_fields, _exhaustive) =
238 record_literal_missing_fields(db, infer, expr_id, &body[expr_id])?;
239 let res = self.missing_fields(db, krate, substs, variant, missing_fields);
240 Some(res)
241 }
242
243 pub(crate) fn record_pattern_missing_fields(
244 &self,
245 db: &dyn HirDatabase,
246 pattern: &ast::RecordPat,
247 ) -> Option<Vec<(StructField, Type)>> {
248 let krate = self.resolver.krate()?;
249 let body = self.body.as_ref()?;
250 let infer = self.infer.as_ref()?;
251
252 let pat_id = self.pat_id(&pattern.clone().into())?;
253 let substs = match &infer.type_of_pat[pat_id] {
254 Ty::Apply(a_ty) => &a_ty.parameters,
255 _ => return None,
256 };
257
258 let (variant, missing_fields) =
259 record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?;
260 let res = self.missing_fields(db, krate, substs, variant, missing_fields);
261 Some(res)
262 }
263
264 fn missing_fields(
265 &self,
266 db: &dyn HirDatabase,
267 krate: CrateId,
268 substs: &Substs,
269 variant: VariantId,
270 missing_fields: Vec<LocalStructFieldId>,
271 ) -> Vec<(StructField, Type)> {
272 let field_types = db.field_types(variant);
273
274 missing_fields
275 .into_iter()
276 .map(|local_id| {
277 let field = StructFieldId { parent: variant, local_id };
278 let ty = field_types[local_id].clone().subst(substs);
279 (field.into(), Type::new_with_resolver_inner(db, krate, &self.resolver, ty))
280 })
281 .collect()
282 }
283
234 pub(crate) fn expand( 284 pub(crate) fn expand(
235 &self, 285 &self,
236 db: &dyn HirDatabase, 286 db: &dyn HirDatabase,
diff --git a/crates/ra_hir_ty/src/expr.rs b/crates/ra_hir_ty/src/expr.rs
index 1e7395b16..b4592fbf5 100644
--- a/crates/ra_hir_ty/src/expr.rs
+++ b/crates/ra_hir_ty/src/expr.rs
@@ -2,12 +2,8 @@
2 2
3use std::sync::Arc; 3use std::sync::Arc;
4 4
5use hir_def::{ 5use hir_def::{path::path, resolver::HasResolver, AdtId, FunctionId};
6 path::{path, Path}, 6use hir_expand::diagnostics::DiagnosticSink;
7 resolver::HasResolver,
8 AdtId, FunctionId,
9};
10use hir_expand::{diagnostics::DiagnosticSink, name::Name};
11use ra_syntax::ast; 7use ra_syntax::ast;
12use ra_syntax::AstPtr; 8use ra_syntax::AstPtr;
13use rustc_hash::FxHashSet; 9use rustc_hash::FxHashSet;
@@ -29,7 +25,7 @@ pub use hir_def::{
29 ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp, 25 ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp,
30 MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp, 26 MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp,
31 }, 27 },
32 VariantId, 28 LocalStructFieldId, VariantId,
33}; 29};
34 30
35pub struct ExprValidator<'a, 'b: 'a> { 31pub struct ExprValidator<'a, 'b: 'a> {
@@ -50,14 +46,37 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
50 pub fn validate_body(&mut self, db: &dyn HirDatabase) { 46 pub fn validate_body(&mut self, db: &dyn HirDatabase) {
51 let body = db.body(self.func.into()); 47 let body = db.body(self.func.into());
52 48
53 for e in body.exprs.iter() { 49 for (id, expr) in body.exprs.iter() {
54 if let (id, Expr::RecordLit { path, fields, spread }) = e { 50 if let Some((variant_def, missed_fields, true)) =
55 self.validate_record_literal(id, path, fields, *spread, db); 51 record_literal_missing_fields(db, &self.infer, id, expr)
56 } else if let (id, Expr::Match { expr, arms }) = e { 52 {
53 // XXX: only look at source_map if we do have missing fields
54 let (_, source_map) = db.body_with_source_map(self.func.into());
55
56 if let Ok(source_ptr) = source_map.expr_syntax(id) {
57 if let Some(expr) = source_ptr.value.left() {
58 let root = source_ptr.file_syntax(db.upcast());
59 if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) {
60 if let Some(field_list) = record_lit.record_field_list() {
61 let variant_data = variant_data(db.upcast(), variant_def);
62 let missed_fields = missed_fields
63 .into_iter()
64 .map(|idx| variant_data.fields()[idx].name.clone())
65 .collect();
66 self.sink.push(MissingFields {
67 file: source_ptr.file_id,
68 field_list: AstPtr::new(&field_list),
69 missed_fields,
70 })
71 }
72 }
73 }
74 }
75 }
76 if let Expr::Match { expr, arms } = expr {
57 self.validate_match(id, *expr, arms, db, self.infer.clone()); 77 self.validate_match(id, *expr, arms, db, self.infer.clone());
58 } 78 }
59 } 79 }
60
61 let body_expr = &body[body.body_expr]; 80 let body_expr = &body[body.body_expr];
62 if let Expr::Block { tail: Some(t), .. } = body_expr { 81 if let Expr::Block { tail: Some(t), .. } = body_expr {
63 self.validate_results_in_tail_expr(body.body_expr, *t, db); 82 self.validate_results_in_tail_expr(body.body_expr, *t, db);
@@ -146,61 +165,6 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
146 } 165 }
147 } 166 }
148 167
149 fn validate_record_literal(
150 &mut self,
151 id: ExprId,
152 _path: &Option<Path>,
153 fields: &[RecordLitField],
154 spread: Option<ExprId>,
155 db: &dyn HirDatabase,
156 ) {
157 if spread.is_some() {
158 return;
159 };
160 let variant_def: VariantId = match self.infer.variant_resolution_for_expr(id) {
161 Some(VariantId::UnionId(_)) | None => return,
162 Some(it) => it,
163 };
164 if let VariantId::UnionId(_) = variant_def {
165 return;
166 }
167
168 let variant_data = variant_data(db.upcast(), variant_def);
169
170 let lit_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
171 let missed_fields: Vec<Name> = variant_data
172 .fields()
173 .iter()
174 .filter_map(|(_f, d)| {
175 let name = d.name.clone();
176 if lit_fields.contains(&name) {
177 None
178 } else {
179 Some(name)
180 }
181 })
182 .collect();
183 if missed_fields.is_empty() {
184 return;
185 }
186 let (_, source_map) = db.body_with_source_map(self.func.into());
187
188 if let Ok(source_ptr) = source_map.expr_syntax(id) {
189 if let Some(expr) = source_ptr.value.left() {
190 let root = source_ptr.file_syntax(db.upcast());
191 if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) {
192 if let Some(field_list) = record_lit.record_field_list() {
193 self.sink.push(MissingFields {
194 file: source_ptr.file_id,
195 field_list: AstPtr::new(&field_list),
196 missed_fields,
197 })
198 }
199 }
200 }
201 }
202 }
203
204 fn validate_results_in_tail_expr(&mut self, body_id: ExprId, id: ExprId, db: &dyn HirDatabase) { 168 fn validate_results_in_tail_expr(&mut self, body_id: ExprId, id: ExprId, db: &dyn HirDatabase) {
205 // the mismatch will be on the whole block currently 169 // the mismatch will be on the whole block currently
206 let mismatch = match self.infer.type_mismatch_for_expr(body_id) { 170 let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
@@ -233,3 +197,63 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
233 } 197 }
234 } 198 }
235} 199}
200
201pub fn record_literal_missing_fields(
202 db: &dyn HirDatabase,
203 infer: &InferenceResult,
204 id: ExprId,
205 expr: &Expr,
206) -> Option<(VariantId, Vec<LocalStructFieldId>, /*exhaustive*/ bool)> {
207 let (fields, exhausitve) = match expr {
208 Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
209 _ => return None,
210 };
211
212 let variant_def = infer.variant_resolution_for_expr(id)?;
213 if let VariantId::UnionId(_) = variant_def {
214 return None;
215 }
216
217 let variant_data = variant_data(db.upcast(), variant_def);
218
219 let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
220 let missed_fields: Vec<LocalStructFieldId> = variant_data
221 .fields()
222 .iter()
223 .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
224 .collect();
225 if missed_fields.is_empty() {
226 return None;
227 }
228 Some((variant_def, missed_fields, exhausitve))
229}
230
231pub fn record_pattern_missing_fields(
232 db: &dyn HirDatabase,
233 infer: &InferenceResult,
234 id: PatId,
235 pat: &Pat,
236) -> Option<(VariantId, Vec<LocalStructFieldId>)> {
237 let fields = match pat {
238 Pat::Record { path: _, args } => args,
239 _ => return None,
240 };
241
242 let variant_def = infer.variant_resolution_for_pat(id)?;
243 if let VariantId::UnionId(_) = variant_def {
244 return None;
245 }
246
247 let variant_data = variant_data(db.upcast(), variant_def);
248
249 let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
250 let missed_fields: Vec<LocalStructFieldId> = variant_data
251 .fields()
252 .iter()
253 .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
254 .collect();
255 if missed_fields.is_empty() {
256 return None;
257 }
258 Some((variant_def, missed_fields))
259}
diff --git a/crates/ra_ide/src/completion/complete_record.rs b/crates/ra_ide/src/completion/complete_record.rs
index 2352ced5f..f46bcee5c 100644
--- a/crates/ra_ide/src/completion/complete_record.rs
+++ b/crates/ra_ide/src/completion/complete_record.rs
@@ -1,60 +1,19 @@
1//! Complete fields in record literals and patterns. 1//! Complete fields in record literals and patterns.
2use ra_syntax::{ast, ast::NameOwner, SmolStr};
3
4use crate::completion::{CompletionContext, Completions}; 2use crate::completion::{CompletionContext, Completions};
5 3
6pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 4pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
7 let (ty, variant, already_present_fields) = 5 let missing_fields = match (ctx.record_lit_pat.as_ref(), ctx.record_lit_syntax.as_ref()) {
8 match (ctx.record_lit_pat.as_ref(), ctx.record_lit_syntax.as_ref()) { 6 (None, None) => return None,
9 (None, None) => return None, 7 (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"),
10 (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"), 8 (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat),
11 (Some(record_pat), _) => ( 9 (_, Some(record_lit)) => ctx.sema.record_literal_missing_fields(record_lit),
12 ctx.sema.type_of_pat(&record_pat.clone().into())?, 10 };
13 ctx.sema.resolve_record_pattern(record_pat)?,
14 pattern_ascribed_fields(record_pat),
15 ),
16 (_, Some(record_lit)) => (
17 ctx.sema.type_of_expr(&record_lit.clone().into())?,
18 ctx.sema.resolve_record_literal(record_lit)?,
19 literal_ascribed_fields(record_lit),
20 ),
21 };
22 11
23 for (field, field_ty) in ty.variant_fields(ctx.db, variant).into_iter().filter(|(field, _)| { 12 for (field, ty) in missing_fields {
24 // FIXME: already_present_names better be `Vec<hir::Name>` 13 acc.add_field(ctx, field, &ty)
25 !already_present_fields.contains(&SmolStr::from(field.name(ctx.db).to_string()))
26 }) {
27 acc.add_field(ctx, field, &field_ty);
28 } 14 }
29 Some(())
30}
31 15
32fn literal_ascribed_fields(record_lit: &ast::RecordLit) -> Vec<SmolStr> { 16 Some(())
33 record_lit
34 .record_field_list()
35 .map(|field_list| field_list.fields())
36 .map(|fields| {
37 fields
38 .into_iter()
39 .filter_map(|field| field.name_ref())
40 .map(|name_ref| name_ref.text().clone())
41 .collect()
42 })
43 .unwrap_or_default()
44}
45
46fn pattern_ascribed_fields(record_pat: &ast::RecordPat) -> Vec<SmolStr> {
47 record_pat
48 .record_field_pat_list()
49 .map(|pat_list| {
50 pat_list
51 .record_field_pats()
52 .filter_map(|fild_pat| fild_pat.name())
53 .chain(pat_list.bind_pats().filter_map(|bind_pat| bind_pat.name()))
54 .map(|name| name.text().clone())
55 .collect()
56 })
57 .unwrap_or_default()
58} 17}
59 18
60#[cfg(test)] 19#[cfg(test)]