diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_hir_def/src/adt.rs | 57 | ||||
-rw-r--r-- | crates/ra_hir_def/src/attr.rs | 5 | ||||
-rw-r--r-- | crates/ra_hir_def/src/body/lower.rs | 51 | ||||
-rw-r--r-- | crates/ra_hir_def/src/data.rs | 14 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/tests.rs | 60 |
5 files changed, 142 insertions, 45 deletions
diff --git a/crates/ra_hir_def/src/adt.rs b/crates/ra_hir_def/src/adt.rs index 7fc4cd76e..be4b0accb 100644 --- a/crates/ra_hir_def/src/adt.rs +++ b/crates/ra_hir_def/src/adt.rs | |||
@@ -4,6 +4,7 @@ use std::sync::Arc; | |||
4 | 4 | ||
5 | use either::Either; | 5 | use either::Either; |
6 | use hir_expand::{ | 6 | use hir_expand::{ |
7 | hygiene::Hygiene, | ||
7 | name::{AsName, Name}, | 8 | name::{AsName, Name}, |
8 | InFile, | 9 | InFile, |
9 | }; | 10 | }; |
@@ -12,9 +13,9 @@ use ra_prof::profile; | |||
12 | use ra_syntax::ast::{self, NameOwner, TypeAscriptionOwner, VisibilityOwner}; | 13 | use ra_syntax::ast::{self, NameOwner, TypeAscriptionOwner, VisibilityOwner}; |
13 | 14 | ||
14 | use crate::{ | 15 | use crate::{ |
15 | db::DefDatabase, src::HasChildSource, src::HasSource, trace::Trace, type_ref::TypeRef, | 16 | attr::Attrs, db::DefDatabase, src::HasChildSource, src::HasSource, trace::Trace, |
16 | visibility::RawVisibility, EnumId, LocalEnumVariantId, LocalStructFieldId, Lookup, StructId, | 17 | type_ref::TypeRef, visibility::RawVisibility, EnumId, HasModule, LocalEnumVariantId, |
17 | UnionId, VariantId, | 18 | LocalStructFieldId, Lookup, ModuleId, StructId, UnionId, VariantId, |
18 | }; | 19 | }; |
19 | 20 | ||
20 | /// Note that we use `StructData` for unions as well! | 21 | /// Note that we use `StructData` for unions as well! |
@@ -56,7 +57,8 @@ impl StructData { | |||
56 | let src = id.lookup(db).source(db); | 57 | let src = id.lookup(db).source(db); |
57 | 58 | ||
58 | let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); | 59 | let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); |
59 | let variant_data = VariantData::new(db, src.map(|s| s.kind())); | 60 | let variant_data = |
61 | VariantData::new(db, src.map(|s| s.kind()), id.lookup(db).container.module(db)); | ||
60 | let variant_data = Arc::new(variant_data); | 62 | let variant_data = Arc::new(variant_data); |
61 | Arc::new(StructData { name, variant_data }) | 63 | Arc::new(StructData { name, variant_data }) |
62 | } | 64 | } |
@@ -70,6 +72,7 @@ impl StructData { | |||
70 | .map(ast::StructKind::Record) | 72 | .map(ast::StructKind::Record) |
71 | .unwrap_or(ast::StructKind::Unit) | 73 | .unwrap_or(ast::StructKind::Unit) |
72 | }), | 74 | }), |
75 | id.lookup(db).container.module(db), | ||
73 | ); | 76 | ); |
74 | let variant_data = Arc::new(variant_data); | 77 | let variant_data = Arc::new(variant_data); |
75 | Arc::new(StructData { name, variant_data }) | 78 | Arc::new(StructData { name, variant_data }) |
@@ -82,7 +85,7 @@ impl EnumData { | |||
82 | let src = e.lookup(db).source(db); | 85 | let src = e.lookup(db).source(db); |
83 | let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); | 86 | let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); |
84 | let mut trace = Trace::new_for_arena(); | 87 | let mut trace = Trace::new_for_arena(); |
85 | lower_enum(db, &mut trace, &src); | 88 | lower_enum(db, &mut trace, &src, e.lookup(db).container.module(db)); |
86 | Arc::new(EnumData { name, variants: trace.into_arena() }) | 89 | Arc::new(EnumData { name, variants: trace.into_arena() }) |
87 | } | 90 | } |
88 | 91 | ||
@@ -98,7 +101,7 @@ impl HasChildSource for EnumId { | |||
98 | fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> { | 101 | fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> { |
99 | let src = self.lookup(db).source(db); | 102 | let src = self.lookup(db).source(db); |
100 | let mut trace = Trace::new_for_map(); | 103 | let mut trace = Trace::new_for_map(); |
101 | lower_enum(db, &mut trace, &src); | 104 | lower_enum(db, &mut trace, &src, self.lookup(db).container.module(db)); |
102 | src.with_value(trace.into_map()) | 105 | src.with_value(trace.into_map()) |
103 | } | 106 | } |
104 | } | 107 | } |
@@ -107,22 +110,23 @@ fn lower_enum( | |||
107 | db: &dyn DefDatabase, | 110 | db: &dyn DefDatabase, |
108 | trace: &mut Trace<EnumVariantData, ast::EnumVariant>, | 111 | trace: &mut Trace<EnumVariantData, ast::EnumVariant>, |
109 | ast: &InFile<ast::EnumDef>, | 112 | ast: &InFile<ast::EnumDef>, |
113 | module_id: ModuleId, | ||
110 | ) { | 114 | ) { |
111 | for var in ast.value.variant_list().into_iter().flat_map(|it| it.variants()) { | 115 | for var in ast.value.variant_list().into_iter().flat_map(|it| it.variants()) { |
112 | trace.alloc( | 116 | trace.alloc( |
113 | || var.clone(), | 117 | || var.clone(), |
114 | || EnumVariantData { | 118 | || EnumVariantData { |
115 | name: var.name().map_or_else(Name::missing, |it| it.as_name()), | 119 | name: var.name().map_or_else(Name::missing, |it| it.as_name()), |
116 | variant_data: Arc::new(VariantData::new(db, ast.with_value(var.kind()))), | 120 | variant_data: Arc::new(VariantData::new(db, ast.with_value(var.kind()), module_id)), |
117 | }, | 121 | }, |
118 | ); | 122 | ); |
119 | } | 123 | } |
120 | } | 124 | } |
121 | 125 | ||
122 | impl VariantData { | 126 | impl VariantData { |
123 | fn new(db: &dyn DefDatabase, flavor: InFile<ast::StructKind>) -> Self { | 127 | fn new(db: &dyn DefDatabase, flavor: InFile<ast::StructKind>, module_id: ModuleId) -> Self { |
124 | let mut trace = Trace::new_for_arena(); | 128 | let mut trace = Trace::new_for_arena(); |
125 | match lower_struct(db, &mut trace, &flavor) { | 129 | match lower_struct(db, &mut trace, &flavor, module_id) { |
126 | StructKind::Tuple => VariantData::Tuple(trace.into_arena()), | 130 | StructKind::Tuple => VariantData::Tuple(trace.into_arena()), |
127 | StructKind::Record => VariantData::Record(trace.into_arena()), | 131 | StructKind::Record => VariantData::Record(trace.into_arena()), |
128 | StructKind::Unit => VariantData::Unit, | 132 | StructKind::Unit => VariantData::Unit, |
@@ -155,22 +159,27 @@ impl HasChildSource for VariantId { | |||
155 | type Value = Either<ast::TupleFieldDef, ast::RecordFieldDef>; | 159 | type Value = Either<ast::TupleFieldDef, ast::RecordFieldDef>; |
156 | 160 | ||
157 | fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> { | 161 | fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> { |
158 | let src = match self { | 162 | let (src, module_id) = match self { |
159 | VariantId::EnumVariantId(it) => { | 163 | VariantId::EnumVariantId(it) => { |
160 | // I don't really like the fact that we call into parent source | 164 | // I don't really like the fact that we call into parent source |
161 | // here, this might add to more queries then necessary. | 165 | // here, this might add to more queries then necessary. |
162 | let src = it.parent.child_source(db); | 166 | let src = it.parent.child_source(db); |
163 | src.map(|map| map[it.local_id].kind()) | 167 | (src.map(|map| map[it.local_id].kind()), it.parent.lookup(db).container.module(db)) |
164 | } | 168 | } |
165 | VariantId::StructId(it) => it.lookup(db).source(db).map(|it| it.kind()), | 169 | VariantId::StructId(it) => { |
166 | VariantId::UnionId(it) => it.lookup(db).source(db).map(|it| { | 170 | (it.lookup(db).source(db).map(|it| it.kind()), it.lookup(db).container.module(db)) |
167 | it.record_field_def_list() | 171 | } |
168 | .map(ast::StructKind::Record) | 172 | VariantId::UnionId(it) => ( |
169 | .unwrap_or(ast::StructKind::Unit) | 173 | it.lookup(db).source(db).map(|it| { |
170 | }), | 174 | it.record_field_def_list() |
175 | .map(ast::StructKind::Record) | ||
176 | .unwrap_or(ast::StructKind::Unit) | ||
177 | }), | ||
178 | it.lookup(db).container.module(db), | ||
179 | ), | ||
171 | }; | 180 | }; |
172 | let mut trace = Trace::new_for_map(); | 181 | let mut trace = Trace::new_for_map(); |
173 | lower_struct(db, &mut trace, &src); | 182 | lower_struct(db, &mut trace, &src, module_id); |
174 | src.with_value(trace.into_map()) | 183 | src.with_value(trace.into_map()) |
175 | } | 184 | } |
176 | } | 185 | } |
@@ -186,10 +195,17 @@ fn lower_struct( | |||
186 | db: &dyn DefDatabase, | 195 | db: &dyn DefDatabase, |
187 | trace: &mut Trace<StructFieldData, Either<ast::TupleFieldDef, ast::RecordFieldDef>>, | 196 | trace: &mut Trace<StructFieldData, Either<ast::TupleFieldDef, ast::RecordFieldDef>>, |
188 | ast: &InFile<ast::StructKind>, | 197 | ast: &InFile<ast::StructKind>, |
198 | module_id: ModuleId, | ||
189 | ) -> StructKind { | 199 | ) -> StructKind { |
200 | let crate_graph = db.crate_graph(); | ||
190 | match &ast.value { | 201 | match &ast.value { |
191 | ast::StructKind::Tuple(fl) => { | 202 | ast::StructKind::Tuple(fl) => { |
192 | for (i, fd) in fl.fields().enumerate() { | 203 | for (i, fd) in fl.fields().enumerate() { |
204 | let attrs = Attrs::new(&fd, &Hygiene::new(db.upcast(), ast.file_id)); | ||
205 | if !attrs.is_cfg_enabled(&crate_graph[module_id.krate].cfg_options) { | ||
206 | continue; | ||
207 | } | ||
208 | |||
193 | trace.alloc( | 209 | trace.alloc( |
194 | || Either::Left(fd.clone()), | 210 | || Either::Left(fd.clone()), |
195 | || StructFieldData { | 211 | || StructFieldData { |
@@ -203,6 +219,11 @@ fn lower_struct( | |||
203 | } | 219 | } |
204 | ast::StructKind::Record(fl) => { | 220 | ast::StructKind::Record(fl) => { |
205 | for fd in fl.fields() { | 221 | for fd in fl.fields() { |
222 | let attrs = Attrs::new(&fd, &Hygiene::new(db.upcast(), ast.file_id)); | ||
223 | if !attrs.is_cfg_enabled(&crate_graph[module_id.krate].cfg_options) { | ||
224 | continue; | ||
225 | } | ||
226 | |||
206 | trace.alloc( | 227 | trace.alloc( |
207 | || Either::Right(fd.clone()), | 228 | || Either::Right(fd.clone()), |
208 | || StructFieldData { | 229 | || StructFieldData { |
diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs index 71a18f5e1..7b0c506b1 100644 --- a/crates/ra_hir_def/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs | |||
@@ -5,6 +5,7 @@ use std::{ops, sync::Arc}; | |||
5 | use either::Either; | 5 | use either::Either; |
6 | use hir_expand::{hygiene::Hygiene, AstId, InFile}; | 6 | use hir_expand::{hygiene::Hygiene, AstId, InFile}; |
7 | use mbe::ast_to_token_tree; | 7 | use mbe::ast_to_token_tree; |
8 | use ra_cfg::CfgOptions; | ||
8 | use ra_syntax::{ | 9 | use ra_syntax::{ |
9 | ast::{self, AstNode, AttrsOwner}, | 10 | ast::{self, AstNode, AttrsOwner}, |
10 | SmolStr, | 11 | SmolStr, |
@@ -90,6 +91,10 @@ impl Attrs { | |||
90 | pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { | 91 | pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { |
91 | AttrQuery { attrs: self, key } | 92 | AttrQuery { attrs: self, key } |
92 | } | 93 | } |
94 | |||
95 | pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool { | ||
96 | self.by_key("cfg").tt_values().all(|tt| cfg_options.is_cfg_enabled(tt) != Some(false)) | ||
97 | } | ||
93 | } | 98 | } |
94 | 99 | ||
95 | #[derive(Debug, Clone, PartialEq, Eq)] | 100 | #[derive(Debug, Clone, PartialEq, Eq)] |
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs index b02de5d67..032037c8c 100644 --- a/crates/ra_hir_def/src/body/lower.rs +++ b/crates/ra_hir_def/src/body/lower.rs | |||
@@ -4,6 +4,7 @@ | |||
4 | use either::Either; | 4 | use either::Either; |
5 | 5 | ||
6 | use hir_expand::{ | 6 | use hir_expand::{ |
7 | hygiene::Hygiene, | ||
7 | name::{name, AsName, Name}, | 8 | name::{name, AsName, Name}, |
8 | MacroDefId, MacroDefKind, | 9 | MacroDefId, MacroDefKind, |
9 | }; | 10 | }; |
@@ -20,6 +21,7 @@ use test_utils::tested_by; | |||
20 | use super::{ExprSource, PatSource}; | 21 | use super::{ExprSource, PatSource}; |
21 | use crate::{ | 22 | use crate::{ |
22 | adt::StructKind, | 23 | adt::StructKind, |
24 | attr::Attrs, | ||
23 | body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax}, | 25 | body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax}, |
24 | builtin_type::{BuiltinFloat, BuiltinInt}, | 26 | builtin_type::{BuiltinFloat, BuiltinInt}, |
25 | db::DefDatabase, | 27 | db::DefDatabase, |
@@ -31,8 +33,8 @@ use crate::{ | |||
31 | path::GenericArgs, | 33 | path::GenericArgs, |
32 | path::Path, | 34 | path::Path, |
33 | type_ref::{Mutability, TypeRef}, | 35 | type_ref::{Mutability, TypeRef}, |
34 | AdtId, ConstLoc, ContainerId, DefWithBodyId, EnumLoc, FunctionLoc, Intern, ModuleDefId, | 36 | AdtId, ConstLoc, ContainerId, DefWithBodyId, EnumLoc, FunctionLoc, HasModule, Intern, |
35 | StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, | 37 | ModuleDefId, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, |
36 | }; | 38 | }; |
37 | 39 | ||
38 | pub(super) fn lower( | 40 | pub(super) fn lower( |
@@ -298,28 +300,41 @@ impl ExprCollector<'_> { | |||
298 | self.alloc_expr(Expr::Return { expr }, syntax_ptr) | 300 | self.alloc_expr(Expr::Return { expr }, syntax_ptr) |
299 | } | 301 | } |
300 | ast::Expr::RecordLit(e) => { | 302 | ast::Expr::RecordLit(e) => { |
303 | let crate_graph = self.db.crate_graph(); | ||
301 | let path = e.path().and_then(|path| self.expander.parse_path(path)); | 304 | let path = e.path().and_then(|path| self.expander.parse_path(path)); |
302 | let mut field_ptrs = Vec::new(); | 305 | let mut field_ptrs = Vec::new(); |
303 | let record_lit = if let Some(nfl) = e.record_field_list() { | 306 | let record_lit = if let Some(nfl) = e.record_field_list() { |
304 | let fields = nfl | 307 | let fields = nfl |
305 | .fields() | 308 | .fields() |
306 | .inspect(|field| field_ptrs.push(AstPtr::new(field))) | 309 | .inspect(|field| field_ptrs.push(AstPtr::new(field))) |
307 | .map(|field| RecordLitField { | 310 | .filter_map(|field| { |
308 | name: field | 311 | let module_id = ContainerId::DefWithBodyId(self.def).module(self.db); |
309 | .name_ref() | 312 | let attrs = Attrs::new( |
310 | .map(|nr| nr.as_name()) | 313 | &field, |
311 | .unwrap_or_else(Name::missing), | 314 | &Hygiene::new(self.db.upcast(), self.expander.current_file_id), |
312 | expr: if let Some(e) = field.expr() { | 315 | ); |
313 | self.collect_expr(e) | 316 | |
314 | } else if let Some(nr) = field.name_ref() { | 317 | if !attrs.is_cfg_enabled(&crate_graph[module_id.krate].cfg_options) { |
315 | // field shorthand | 318 | return None; |
316 | self.alloc_expr_field_shorthand( | 319 | } |
317 | Expr::Path(Path::from_name_ref(&nr)), | 320 | |
318 | AstPtr::new(&field), | 321 | Some(RecordLitField { |
319 | ) | 322 | name: field |
320 | } else { | 323 | .name_ref() |
321 | self.missing_expr() | 324 | .map(|nr| nr.as_name()) |
322 | }, | 325 | .unwrap_or_else(Name::missing), |
326 | expr: if let Some(e) = field.expr() { | ||
327 | self.collect_expr(e) | ||
328 | } else if let Some(nr) = field.name_ref() { | ||
329 | // field shorthand | ||
330 | self.alloc_expr_field_shorthand( | ||
331 | Expr::Path(Path::from_name_ref(&nr)), | ||
332 | AstPtr::new(&field), | ||
333 | ) | ||
334 | } else { | ||
335 | self.missing_expr() | ||
336 | }, | ||
337 | }) | ||
323 | }) | 338 | }) |
324 | .collect(); | 339 | .collect(); |
325 | let spread = nfl.spread().map(|s| self.collect_expr(s)); | 340 | let spread = nfl.spread().map(|s| self.collect_expr(s)); |
diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs index 606ec48b0..e793ad874 100644 --- a/crates/ra_hir_def/src/data.rs +++ b/crates/ra_hir_def/src/data.rs | |||
@@ -7,7 +7,6 @@ use hir_expand::{ | |||
7 | name::{name, AsName, Name}, | 7 | name::{name, AsName, Name}, |
8 | AstId, InFile, | 8 | AstId, InFile, |
9 | }; | 9 | }; |
10 | use ra_cfg::CfgOptions; | ||
11 | use ra_prof::profile; | 10 | use ra_prof::profile; |
12 | use ra_syntax::ast::{ | 11 | use ra_syntax::ast::{ |
13 | self, AstNode, ImplItem, ModuleItemOwner, NameOwner, TypeAscriptionOwner, VisibilityOwner, | 12 | self, AstNode, ImplItem, ModuleItemOwner, NameOwner, TypeAscriptionOwner, VisibilityOwner, |
@@ -318,10 +317,6 @@ fn collect_impl_items_in_macro( | |||
318 | } | 317 | } |
319 | } | 318 | } |
320 | 319 | ||
321 | fn is_cfg_enabled(cfg_options: &CfgOptions, attrs: &Attrs) -> bool { | ||
322 | attrs.by_key("cfg").tt_values().all(|tt| cfg_options.is_cfg_enabled(tt) != Some(false)) | ||
323 | } | ||
324 | |||
325 | fn collect_impl_items( | 320 | fn collect_impl_items( |
326 | db: &dyn DefDatabase, | 321 | db: &dyn DefDatabase, |
327 | impl_items: impl Iterator<Item = ImplItem>, | 322 | impl_items: impl Iterator<Item = ImplItem>, |
@@ -341,10 +336,11 @@ fn collect_impl_items( | |||
341 | } | 336 | } |
342 | .intern(db); | 337 | .intern(db); |
343 | 338 | ||
344 | if !is_cfg_enabled( | 339 | if !db |
345 | &crate_graph[module_id.krate].cfg_options, | 340 | .function_data(def) |
346 | &db.function_data(def).attrs, | 341 | .attrs |
347 | ) { | 342 | .is_cfg_enabled(&crate_graph[module_id.krate].cfg_options) |
343 | { | ||
348 | None | 344 | None |
349 | } else { | 345 | } else { |
350 | Some(def.into()) | 346 | Some(def.into()) |
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs index ac0966236..08723068f 100644 --- a/crates/ra_hir_ty/src/tests.rs +++ b/crates/ra_hir_ty/src/tests.rs | |||
@@ -349,3 +349,63 @@ fn no_such_field_with_feature_flag_diagnostics() { | |||
349 | 349 | ||
350 | assert_snapshot!(diagnostics, @r###""###); | 350 | assert_snapshot!(diagnostics, @r###""###); |
351 | } | 351 | } |
352 | |||
353 | #[test] | ||
354 | fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() { | ||
355 | let diagnostics = TestDB::with_files( | ||
356 | r#" | ||
357 | //- /lib.rs crate:foo cfg:feature=foo | ||
358 | struct S { | ||
359 | #[cfg(feature = "foo")] | ||
360 | foo: u32, | ||
361 | #[cfg(not(feature = "foo"))] | ||
362 | bar: u32, | ||
363 | } | ||
364 | |||
365 | impl S { | ||
366 | #[cfg(feature = "foo")] | ||
367 | fn new(foo: u32) -> Self { | ||
368 | Self { foo } | ||
369 | } | ||
370 | #[cfg(not(feature = "foo"))] | ||
371 | fn new(bar: u32) -> Self { | ||
372 | Self { bar } | ||
373 | } | ||
374 | } | ||
375 | "#, | ||
376 | ) | ||
377 | .diagnostics() | ||
378 | .0; | ||
379 | |||
380 | assert_snapshot!(diagnostics, @r###""###); | ||
381 | } | ||
382 | |||
383 | #[test] | ||
384 | fn no_such_field_with_feature_flag_diagnostics_on_struct_fields() { | ||
385 | let diagnostics = TestDB::with_files( | ||
386 | r#" | ||
387 | //- /lib.rs crate:foo cfg:feature=foo | ||
388 | struct S { | ||
389 | #[cfg(feature = "foo")] | ||
390 | foo: u32, | ||
391 | #[cfg(not(feature = "foo"))] | ||
392 | bar: u32, | ||
393 | } | ||
394 | |||
395 | impl S { | ||
396 | fn new(val: u32) -> Self { | ||
397 | Self { | ||
398 | #[cfg(feature = "foo")] | ||
399 | foo: val, | ||
400 | #[cfg(not(feature = "foo"))] | ||
401 | bar: val, | ||
402 | } | ||
403 | } | ||
404 | } | ||
405 | "#, | ||
406 | ) | ||
407 | .diagnostics() | ||
408 | .0; | ||
409 | |||
410 | assert_snapshot!(diagnostics, @r###""###); | ||
411 | } | ||