1use std::collections::{HashMap, HashSet};
6use std::fmt;
7use std::fs;
8use std::path::{Path, PathBuf};
9
10use syn::Item;
11use syn::ext::IdentExt;
12use syn::spanned::Spanned;
13use syn::visit::{self, Visit};
14
15use crate::crate_root::CrateRoot;
16use crate::description::{self, Description};
17use crate::doc_links::{self, PkgLink};
18use crate::helpers::{
19 extract_cfg_attrs, extract_path_attr, extract_roxygen_tags, has_derive, has_miniextendr_attr,
20 impl_type_name, is_altrep_struct, parse_miniextendr_impl_attrs,
21};
22
23#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for MethodReceiverKind { }
#[automatically_derived]
impl ::core::clone::Clone for MethodReceiverKind {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for MethodReceiverKind { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for MethodReceiverKind {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
MethodReceiverKind::None => "None",
MethodReceiverKind::Ref => "Ref",
MethodReceiverKind::RefMut => "RefMut",
MethodReceiverKind::Value => "Value",
MethodReceiverKind::ExternalPtrRef => "ExternalPtrRef",
MethodReceiverKind::ExternalPtrMut => "ExternalPtrMut",
MethodReceiverKind::ExternalPtrValue => "ExternalPtrValue",
})
}
}Debug, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for MethodReceiverKind { }
#[automatically_derived]
impl ::core::cmp::PartialEq for MethodReceiverKind {
#[inline]
fn eq(&self, other: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for MethodReceiverKind { }Eq)]
30pub enum MethodReceiverKind {
31 None,
33 Ref,
35 RefMut,
37 Value,
39 ExternalPtrRef,
41 ExternalPtrMut,
43 ExternalPtrValue,
45}
46
47impl MethodReceiverKind {
48 pub fn is_instance(self) -> bool {
56 #[allow(non_exhaustive_omitted_patterns)] match self {
Self::Ref | Self::RefMut | Self::ExternalPtrRef | Self::ExternalPtrMut |
Self::ExternalPtrValue => true,
_ => false,
}matches!(
57 self,
58 Self::Ref
59 | Self::RefMut
60 | Self::ExternalPtrRef
61 | Self::ExternalPtrMut
62 | Self::ExternalPtrValue
63 )
64 }
65
66 pub fn spelling(self) -> &'static str {
68 match self {
69 Self::None => "(none)",
70 Self::Ref => "&self",
71 Self::RefMut => "&mut self",
72 Self::Value => "self",
73 Self::ExternalPtrRef => "self: ExternalPtrRef<'_, Self>",
74 Self::ExternalPtrMut => "self: ExternalPtrMut<'_, Self>",
75 Self::ExternalPtrValue => "self: ExternalPtr<Self>",
76 }
77 }
78}
79
80#[derive(#[automatically_derived]
impl ::core::clone::Clone for ImplMethodEntry {
#[inline]
fn clone(&self) -> Self {
ImplMethodEntry {
method_name: ::core::clone::Clone::clone(&self.method_name),
line: ::core::clone::Clone::clone(&self.line),
class_system: ::core::clone::Clone::clone(&self.class_system),
return_type_str: ::core::clone::Clone::clone(&self.return_type_str),
receiver_kind: ::core::clone::Clone::clone(&self.receiver_kind),
has_constructor_attr: ::core::clone::Clone::clone(&self.has_constructor_attr),
}
}
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for ImplMethodEntry {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["method_name", "line", "class_system", "return_type_str",
"receiver_kind", "has_constructor_attr"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.method_name, &self.line, &self.class_system,
&self.return_type_str, &self.receiver_kind,
&&self.has_constructor_attr];
::core::fmt::Formatter::debug_struct_fields_finish(f,
"ImplMethodEntry", names, values)
}
}Debug)]
82pub struct ImplMethodEntry {
83 pub method_name: String,
84 pub line: usize,
85 pub class_system: String,
86 pub return_type_str: String,
88 pub receiver_kind: MethodReceiverKind,
90 pub has_constructor_attr: bool,
92}
93
94#[derive(#[automatically_derived]
impl ::core::clone::Clone for InherentImpl {
#[inline]
fn clone(&self) -> Self {
InherentImpl {
type_name: ::core::clone::Clone::clone(&self.type_name),
class_system: ::core::clone::Clone::clone(&self.class_system),
line: ::core::clone::Clone::clone(&self.line),
first_line: ::core::clone::Clone::clone(&self.first_line),
allows_mxl121: ::core::clone::Clone::clone(&self.allows_mxl121),
}
}
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for InherentImpl {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field5_finish(f, "InherentImpl",
"type_name", &self.type_name, "class_system", &self.class_system,
"line", &self.line, "first_line", &self.first_line,
"allows_mxl121", &&self.allows_mxl121)
}
}Debug)]
97pub struct InherentImpl {
98 pub type_name: String,
99 pub class_system: String,
100 pub line: usize,
102 pub first_line: usize,
105 pub allows_mxl121: bool,
108}
109
110#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for LintKind { }
#[automatically_derived]
impl ::core::clone::Clone for LintKind {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for LintKind { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for LintKind {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
LintKind::Function => "Function",
LintKind::Impl => "Impl",
LintKind::Struct => "Struct",
LintKind::TraitImpl => "TraitImpl",
LintKind::Vctrs => "Vctrs",
})
}
}Debug, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for LintKind { }
#[automatically_derived]
impl ::core::cmp::PartialEq for LintKind {
#[inline]
fn eq(&self, other: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for LintKind { }Eq, #[automatically_derived]
impl ::core::hash::Hash for LintKind {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
::core::hash::Hash::hash(&::core::intrinsics::discriminant_value(self),
state)
}
}Hash)]
115pub enum LintKind {
116 Function,
117 Impl,
118 Struct,
119 TraitImpl,
120 Vctrs,
121}
122
123#[derive(#[automatically_derived]
impl ::core::clone::Clone for LintItem {
#[inline]
fn clone(&self) -> Self {
LintItem {
kind: ::core::clone::Clone::clone(&self.kind),
name: ::core::clone::Clone::clone(&self.name),
line: ::core::clone::Clone::clone(&self.line),
}
}
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for LintItem {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f, "LintItem",
"kind", &self.kind, "name", &self.name, "line", &&self.line)
}
}Debug)]
124pub struct LintItem {
125 pub kind: LintKind,
126 pub name: String,
127 pub line: usize,
128}
129
130impl PartialEq for LintItem {
131 fn eq(&self, other: &Self) -> bool {
132 self.kind == other.kind && self.name == other.name
133 }
134}
135
136impl Eq for LintItem {}
137
138impl std::hash::Hash for LintItem {
139 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
140 self.kind.hash(state);
141 self.name.hash(state);
142 }
143}
144
145impl LintItem {
146 pub fn new(kind: LintKind, name: String, line: usize) -> Self {
147 Self { kind, name, line }
148 }
149}
150#[derive(#[automatically_derived]
impl ::core::clone::Clone for AttributedTraitImpl {
#[inline]
fn clone(&self) -> Self {
AttributedTraitImpl {
type_name: ::core::clone::Clone::clone(&self.type_name),
trait_name: ::core::clone::Clone::clone(&self.trait_name),
class_system: ::core::clone::Clone::clone(&self.class_system),
line: ::core::clone::Clone::clone(&self.line),
}
}
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for AttributedTraitImpl {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field4_finish(f,
"AttributedTraitImpl", "type_name", &self.type_name, "trait_name",
&self.trait_name, "class_system", &self.class_system, "line",
&&self.line)
}
}Debug)]
155pub struct AttributedTraitImpl {
156 pub type_name: String,
157 pub trait_name: String,
158 pub class_system: Option<String>,
159 pub line: usize,
160}
161#[derive(#[automatically_derived]
impl ::core::fmt::Debug for FileData {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["miniextendr_items", "types_with_external_ptr",
"types_with_typed_external", "inherent_impl_class_systems",
"attributed_trait_impls", "fn_visibility",
"declared_child_mods", "path_redirected_mods",
"mod_decl_cfgs", "export_control", "impl_methods",
"inherent_impls", "returned_types", "hand_built_types",
"fn_doc_tags", "rf_error_calls", "ffi_unchecked_calls",
"vec_into_sexp_calls", "nonapi_attrib_calls",
"miniextendr_bodies", "raw_r_eval_calls", "doc_pkg_links",
"fn_param_names"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.miniextendr_items, &self.types_with_external_ptr,
&self.types_with_typed_external,
&self.inherent_impl_class_systems,
&self.attributed_trait_impls, &self.fn_visibility,
&self.declared_child_mods, &self.path_redirected_mods,
&self.mod_decl_cfgs, &self.export_control,
&self.impl_methods, &self.inherent_impls,
&self.returned_types, &self.hand_built_types,
&self.fn_doc_tags, &self.rf_error_calls,
&self.ffi_unchecked_calls, &self.vec_into_sexp_calls,
&self.nonapi_attrib_calls, &self.miniextendr_bodies,
&self.raw_r_eval_calls, &self.doc_pkg_links,
&&self.fn_param_names];
::core::fmt::Formatter::debug_struct_fields_finish(f, "FileData",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::default::Default for FileData {
#[inline]
fn default() -> Self {
FileData {
miniextendr_items: ::core::default::Default::default(),
types_with_external_ptr: ::core::default::Default::default(),
types_with_typed_external: ::core::default::Default::default(),
inherent_impl_class_systems: ::core::default::Default::default(),
attributed_trait_impls: ::core::default::Default::default(),
fn_visibility: ::core::default::Default::default(),
declared_child_mods: ::core::default::Default::default(),
path_redirected_mods: ::core::default::Default::default(),
mod_decl_cfgs: ::core::default::Default::default(),
export_control: ::core::default::Default::default(),
impl_methods: ::core::default::Default::default(),
inherent_impls: ::core::default::Default::default(),
returned_types: ::core::default::Default::default(),
hand_built_types: ::core::default::Default::default(),
fn_doc_tags: ::core::default::Default::default(),
rf_error_calls: ::core::default::Default::default(),
ffi_unchecked_calls: ::core::default::Default::default(),
vec_into_sexp_calls: ::core::default::Default::default(),
nonapi_attrib_calls: ::core::default::Default::default(),
miniextendr_bodies: ::core::default::Default::default(),
raw_r_eval_calls: ::core::default::Default::default(),
doc_pkg_links: ::core::default::Default::default(),
fn_param_names: ::core::default::Default::default(),
}
}
}Default)]
166pub struct FileData {
167 pub miniextendr_items: Vec<LintItem>,
169
170 pub types_with_external_ptr: HashSet<String>,
172 pub types_with_typed_external: HashSet<String>,
173
174 pub inherent_impl_class_systems: HashMap<String, (String, usize)>,
176 pub attributed_trait_impls: Vec<AttributedTraitImpl>,
177
178 pub fn_visibility: HashMap<String, bool>,
180
181 pub declared_child_mods: Vec<String>,
184 pub path_redirected_mods: Vec<(String, String)>,
186 pub mod_decl_cfgs: HashMap<String, Vec<String>>,
188
189 pub export_control: HashMap<String, (bool, bool, usize)>,
192
193 pub impl_methods: HashMap<String, Vec<ImplMethodEntry>>,
196 pub inherent_impls: Vec<InherentImpl>,
198 pub returned_types: HashSet<String>,
203 pub hand_built_types: HashSet<String>,
206
207 pub fn_doc_tags: HashMap<String, Vec<String>>,
210
211 pub rf_error_calls: Vec<(String, usize)>,
214 pub ffi_unchecked_calls: Vec<(String, usize)>,
216 pub vec_into_sexp_calls: Vec<(String, usize)>,
221 pub nonapi_attrib_calls: Vec<(String, usize)>,
224 pub miniextendr_bodies: Vec<(usize, usize)>,
229 pub raw_r_eval_calls: Vec<(String, usize)>,
233
234 pub doc_pkg_links: Vec<PkgLink>,
237
238 pub fn_param_names: HashMap<String, Vec<(String, usize)>>,
242}
243pub struct CrateIndex {
249 pub files: Vec<PathBuf>,
251 pub file_data: HashMap<PathBuf, FileData>,
253 pub description: Option<Description>,
256 pub prose_links_keep: bool,
259}
260
261#[derive(#[automatically_derived]
impl ::core::fmt::Debug for IndexError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f, "IndexError",
"message", &self.message, "files", &&self.files)
}
}Debug)]
263pub struct IndexError {
264 pub message: String,
266 pub files: Vec<PathBuf>,
270}
271
272impl fmt::Display for IndexError {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 f.write_str(&self.message)
275 }
276}
277
278impl From<String> for IndexError {
279 fn from(message: String) -> Self {
280 Self {
281 message,
282 files: Vec::new(),
283 }
284 }
285}
286
287impl CrateIndex {
288 pub fn build(manifest_dir: &Path) -> Result<Self, IndexError> {
291 let root = CrateRoot::resolve(manifest_dir)?;
292
293 let mut rs_files = Vec::new();
294 collect_rs_files_from_module_tree(&root, &mut rs_files)?;
295 rs_files.sort();
296
297 let mut file_data = HashMap::new();
298 let mut parse_errors = Vec::new();
299
300 for path in &rs_files {
301 match parse_file(path) {
302 Ok(data) => {
303 file_data.insert(path.clone(), data);
304 }
305 Err(err) => parse_errors.push(err),
306 }
307 }
308
309 if !parse_errors.is_empty() {
310 return Err(IndexError {
311 message: parse_errors.join("; "),
312 files: rs_files,
313 });
314 }
315
316 Ok(Self {
317 files: rs_files,
318 file_data,
319 description: description::read(manifest_dir),
320 prose_links_keep: crate::crate_root::prose_links_keep(manifest_dir),
321 })
322 }
323}
324fn collect_rs_files_from_module_tree(
332 root: &CrateRoot,
333 out: &mut Vec<PathBuf>,
334) -> Result<(), String> {
335 if !root.file.is_file() {
336 return Err(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("miniextendr-lint: cannot find crate root {0} ({1})",
root.file.display(), root.origin))
})format!(
337 "miniextendr-lint: cannot find crate root {} ({})",
338 root.file.display(),
339 root.origin
340 ));
341 }
342
343 let active_features = collect_active_cargo_features();
344 let mut seen = HashSet::new();
345 walk_module_file(&root.file, None, &active_features, out, &mut seen);
346 Ok(())
347}
348
349fn collect_active_cargo_features() -> HashSet<String> {
352 std::env::vars()
353 .filter_map(|(key, _)| {
354 key.strip_prefix("CARGO_FEATURE_")
355 .map(|suffix| suffix.to_lowercase().replace('_', "-"))
356 })
357 .collect()
358}
359
360struct ModDir {
363 dir: PathBuf,
366 relative: Option<String>,
371}
372
373impl ModDir {
374 fn child_dir(&self) -> PathBuf {
376 match &self.relative {
377 Some(name) => self.dir.join(name),
378 None => self.dir.clone(),
379 }
380 }
381}
382
383fn walk_module_file(
388 file: &Path,
389 relative: Option<String>,
390 active_features: &HashSet<String>,
391 out: &mut Vec<PathBuf>,
392 seen: &mut HashSet<PathBuf>,
393) {
394 if !file.is_file() {
395 return;
396 }
397
398 let file_buf = file.to_path_buf();
399 if !seen.insert(file_buf.clone()) {
400 return;
401 }
402
403 out.push(file_buf);
404
405 let Ok(src) = fs::read_to_string(file) else {
407 return;
408 };
409 let Ok(parsed) = syn::parse_file(&src) else {
410 return;
411 };
412
413 let Some(dir) = file.parent() else {
414 return;
415 };
416 let scope = ModDir {
417 dir: dir.to_path_buf(),
418 relative,
419 };
420
421 discover_mod_declarations(&parsed.items, &scope, active_features, out, seen);
422}
423
424fn discover_mod_declarations(
426 items: &[Item],
427 scope: &ModDir,
428 active_features: &HashSet<String>,
429 out: &mut Vec<PathBuf>,
430 seen: &mut HashSet<PathBuf>,
431) {
432 for item in items {
433 let Item::Mod(item_mod) = item else {
434 continue;
435 };
436
437 let cfgs = extract_cfg_attrs(&item_mod.attrs);
439 if !cfgs.is_empty() && !is_cfg_active(&cfgs, active_features) {
440 continue;
441 }
442
443 let mod_name = item_mod.ident.unraw().to_string();
445 let path_attr = extract_path_attr(&item_mod.attrs);
446
447 match (&item_mod.content, path_attr) {
448 (Some((_, child_items)), Some(dir)) => {
450 let inner = ModDir {
451 dir: scope.dir.join(dir),
452 relative: None,
453 };
454 discover_mod_declarations(child_items, &inner, active_features, out, seen);
455 }
456 (Some((_, child_items)), None) => {
458 let inner = ModDir {
459 dir: scope.child_dir().join(&mod_name),
460 relative: None,
461 };
462 discover_mod_declarations(child_items, &inner, active_features, out, seen);
463 }
464 (None, Some(file_path)) => {
467 let target = scope.dir.join(file_path);
468 walk_module_file(&target, None, active_features, out, seen);
469 }
470 (None, None) => {
472 let child_dir = scope.child_dir();
473 let sibling = child_dir.join(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}.rs", mod_name))
})format!("{mod_name}.rs"));
474 if sibling.is_file() {
475 walk_module_file(&sibling, Some(mod_name), active_features, out, seen);
476 } else {
477 let subdir_mod = child_dir.join(&mod_name).join("mod.rs");
478 walk_module_file(&subdir_mod, None, active_features, out, seen);
479 }
480 }
481 }
482 }
483}
484
485fn is_cfg_active(cfgs: &[String], active_features: &HashSet<String>) -> bool {
487 for cfg_str in cfgs {
488 if let Some(result) = eval_cfg_str(cfg_str, active_features)
489 && !result
490 {
491 return false;
492 }
493 }
494 true
495}
496
497fn eval_cfg_str(cfg_str: &str, active_features: &HashSet<String>) -> Option<bool> {
499 let normalized: String = cfg_str.chars().filter(|c| !c.is_whitespace()).collect();
500
501 let inner = normalized
502 .strip_prefix("cfg(")
503 .and_then(|s| s.strip_suffix(')'))?;
504
505 if let Some(not_inner) = inner.strip_prefix("not(").and_then(|s| s.strip_suffix(')')) {
506 if let Some(feat) = extract_feature_name(not_inner) {
507 return Some(!active_features.contains(&feat));
508 }
509 return None;
510 }
511
512 if let Some(feat) = extract_feature_name(inner) {
513 return Some(active_features.contains(&feat));
514 }
515
516 None
517}
518
519fn extract_feature_name(s: &str) -> Option<String> {
521 let rest = s.strip_prefix("feature")?;
522 let rest = rest.strip_prefix('=')?;
523 let name = rest.trim_matches('"').trim_matches('\\');
524 if name.is_empty() {
525 None
526 } else {
527 Some(name.to_string())
528 }
529}
530fn parse_file(path: &Path) -> Result<FileData, String> {
535 let src = fs::read_to_string(path)
536 .map_err(|err| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}: failed to read: {1}",
path.display(), err))
})format!("{}: failed to read: {err}", path.display()))?;
537
538 let parsed = syn::parse_file(&src).map_err(|err| {
539 let mut message = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}: failed to parse: {1}",
path.display(), err))
})format!("{}: failed to parse: {err}", path.display());
540 if let Some(hint) = crate::misplaced_dots::hint(&src) {
541 message.push_str("; ");
542 message.push_str(&hint);
543 }
544 message
545 })?;
546
547 let mut data = FileData::default();
548 collect_items_recursive(&parsed.items, &mut data);
549 let mut hand_built = HandBuilt::default();
550 hand_built.visit_file(&parsed);
551 data.hand_built_types = hand_built.types;
552
553 let lines: Vec<&str> = src.lines().collect();
555 scan_rf_error_calls(&lines, &mut data);
556 scan_ffi_unchecked_calls(&lines, &mut data);
557 scan_vec_into_sexp_calls(&lines, &mut data);
558 scan_nonapi_attrib_calls(&lines, &mut data);
559 scan_raw_r_eval_calls(&lines, &mut data);
560 for block in &mut data.inherent_impls {
561 block.allows_mxl121 = header_allows(&lines, block.first_line, block.line, "MXL121");
562 }
563
564 Ok(data)
565}
566
567fn extract_param_names(sig: &syn::Signature) -> Vec<(String, usize)> {
571 let mut params = Vec::new();
572 for input in &sig.inputs {
573 if let syn::FnArg::Typed(pat_type) = input
574 && let syn::Pat::Ident(pat_ident) = &*pat_type.pat
575 {
576 let name = pat_ident.ident.to_string();
577 if name == "_" {
582 continue;
583 }
584 let line = pat_ident.ident.span().start().line;
585 params.push((name, line));
586 }
587 }
588 params
589}
590
591fn collect_doc_pkg_links(item: &Item, data: &mut FileData) {
600 let attrs: &[syn::Attribute] = match item {
601 Item::Fn(i) => &i.attrs,
602 Item::Impl(i) => &i.attrs,
603 Item::Struct(i) => &i.attrs,
604 Item::Enum(i) => &i.attrs,
605 _ => return,
606 };
607 if !has_miniextendr_attr(attrs) {
608 return;
609 }
610 let flags = parse_miniextendr_impl_attrs(attrs);
611 let no_page = match item {
612 Item::Fn(_) => flags.noexport || doc_links::has_tag(attrs, "noRd"),
613 Item::Impl(item_impl) => {
614 doc_links::has_tag(attrs, "noRd")
615 || (flags.noexport
616 && !flags.internal
617 && (item_impl.trait_.is_some()
618 || !doc_links::has_tag(attrs, "keywords internal")))
619 }
620 _ => false,
621 };
622 if no_page {
623 return;
624 }
625 data.doc_pkg_links.extend(doc_links::pkg_links(attrs));
626 if let Item::Impl(item_impl) = item {
627 for impl_item in &item_impl.items {
628 if let syn::ImplItem::Fn(method) = impl_item
629 && !doc_links::has_tag(&method.attrs, "noRd")
630 {
631 data.doc_pkg_links
632 .extend(doc_links::pkg_links(&method.attrs));
633 }
634 }
635 }
636}
637
638fn collect_items_recursive(items: &[Item], data: &mut FileData) {
640 for item in items {
641 collect_doc_pkg_links(item, data);
642 match item {
643 Item::Fn(item_fn) if has_miniextendr_attr(&item_fn.attrs) => {
644 let line = item_fn.sig.ident.span().start().line;
645 let name = item_fn.sig.ident.to_string();
646
647 data.miniextendr_items
648 .push(LintItem::new(LintKind::Function, name.clone(), line));
649
650 let is_pub = #[allow(non_exhaustive_omitted_patterns)] match item_fn.vis {
syn::Visibility::Public(_) => true,
_ => false,
}matches!(item_fn.vis, syn::Visibility::Public(_));
652 data.fn_visibility.insert(name.clone(), is_pub);
653
654 let attrs = parse_miniextendr_impl_attrs(&item_fn.attrs);
656 if attrs.internal || attrs.noexport {
657 data.export_control
658 .insert(name.clone(), (attrs.internal, attrs.noexport, line));
659 }
660
661 let doc_tags = extract_roxygen_tags(&item_fn.attrs);
663 if !doc_tags.is_empty() {
664 data.fn_doc_tags.insert(name.clone(), doc_tags);
665 }
666
667 let params = extract_param_names(&item_fn.sig);
669 if !params.is_empty() {
670 data.fn_param_names.insert(name.clone(), params);
671 }
672
673 if let syn::ReturnType::Type(_, ty) = &item_fn.sig.output {
674 collect_returned_types(ty, None, &mut data.returned_types);
675 }
676
677 data.miniextendr_bodies.push(block_lines(&item_fn.block));
678 }
679 Item::Trait(item_trait) if has_miniextendr_attr(&item_trait.attrs) => {
680 for trait_item in &item_trait.items {
682 if let syn::TraitItem::Fn(method) = trait_item
683 && let Some(block) = &method.default
684 {
685 data.miniextendr_bodies.push(block_lines(block));
686 }
687 }
688 }
689 Item::Struct(item_struct) => {
690 let is_miniextendr_altrep =
691 has_miniextendr_attr(&item_struct.attrs) && is_altrep_struct(item_struct);
692 let is_derive_altrep = has_derive(&item_struct.attrs, "Altrep");
693 if is_miniextendr_altrep || is_derive_altrep {
694 let line = item_struct.ident.span().start().line;
695 data.miniextendr_items.push(LintItem::new(
696 LintKind::Struct,
697 item_struct.ident.to_string(),
698 line,
699 ));
700 }
701 if has_derive(&item_struct.attrs, "ExternalPtr") {
702 data.types_with_external_ptr
703 .insert(item_struct.ident.to_string());
704 }
705 if has_derive(&item_struct.attrs, "Vctrs") {
706 let line = item_struct.ident.span().start().line;
707 data.miniextendr_items.push(LintItem::new(
708 LintKind::Vctrs,
709 item_struct.ident.to_string(),
710 line,
711 ));
712 }
713 }
714 Item::Impl(item_impl) => {
715 if let Some((_, trait_path, _)) = &item_impl.trait_
717 && let Some(last_seg) = trait_path.segments.last()
718 && last_seg.ident == "TypedExternal"
719 && let Some(type_name) = impl_type_name(&item_impl.self_ty)
720 {
721 data.types_with_typed_external.insert(type_name);
722 }
723
724 if has_miniextendr_attr(&item_impl.attrs) {
725 let line = item_impl.self_ty.span().start().line;
726 let impl_attrs = parse_miniextendr_impl_attrs(&item_impl.attrs);
727
728 for impl_item in &item_impl.items {
730 if let syn::ImplItem::Fn(method) = impl_item {
731 data.miniextendr_bodies.push(block_lines(&method.block));
732 }
733 }
734
735 match impl_type_name(&item_impl.self_ty) {
736 Some(type_name) => {
737 if let Some((_, trait_path, _)) = &item_impl.trait_ {
738 if let Some(trait_seg) = trait_path.segments.last() {
740 let trait_name = trait_seg.ident.to_string();
741 let full_name = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0} for {1}", trait_name,
type_name))
})format!("{} for {}", trait_name, type_name);
742 data.miniextendr_items.push(LintItem::new(
743 LintKind::TraitImpl,
744 full_name,
745 line,
746 ));
747 data.attributed_trait_impls.push(AttributedTraitImpl {
748 type_name: type_name.clone(),
749 trait_name,
750 class_system: impl_attrs.class_system.clone(),
751 line,
752 });
753 }
754 } else {
755 let class_system =
757 impl_attrs.class_system.clone().unwrap_or_default();
758 data.inherent_impl_class_systems
759 .insert(type_name.clone(), (class_system.clone(), line));
760 data.miniextendr_items.push(LintItem::new(
761 LintKind::Impl,
762 type_name.clone(),
763 line,
764 ));
765 let impl_line = item_impl.impl_token.span.start().line;
766 data.inherent_impls.push(InherentImpl {
767 type_name: type_name.clone(),
768 class_system: class_system.clone(),
769 line: impl_line,
770 first_line: item_impl
771 .attrs
772 .first()
773 .map_or(impl_line, |attr| attr.span().start().line),
774 allows_mxl121: false,
775 });
776
777 let methods =
779 data.impl_methods.entry(type_name.clone()).or_default();
780 for impl_item in &item_impl.items {
781 if let syn::ImplItem::Fn(method) = impl_item {
782 let method_name = method.sig.ident.to_string();
783 let method_line = method.sig.ident.span().start().line;
784 let return_type_str =
785 extract_return_type_str(&method.sig.output);
786 let receiver_kind = detect_receiver_kind(&method.sig);
787 let has_constructor_attr =
788 has_constructor_attr(&method.attrs);
789 methods.push(ImplMethodEntry {
790 method_name,
791 line: method_line,
792 class_system: class_system.clone(),
793 return_type_str,
794 receiver_kind,
795 has_constructor_attr,
796 });
797 }
798 }
799
800 if impl_attrs.internal || impl_attrs.noexport {
802 data.export_control.insert(
803 type_name.clone(),
804 (impl_attrs.internal, impl_attrs.noexport, line),
805 );
806 }
807 }
808
809 for impl_item in &item_impl.items {
811 if let syn::ImplItem::Fn(method) = impl_item {
812 let method_name = method.sig.ident.to_string();
813 let key = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}::{1}", type_name, method_name))
})format!("{}::{}", type_name, method_name);
814 let params = extract_param_names(&method.sig);
815 if !params.is_empty() {
816 data.fn_param_names.insert(key, params);
817 }
818 }
819 }
820
821 for impl_item in &item_impl.items {
823 let syn::ImplItem::Fn(method) = impl_item else {
824 continue;
825 };
826 let syn::ReturnType::Type(_, ty) = &method.sig.output else {
827 continue;
828 };
829 let mut returned = HashSet::new();
830 collect_returned_types(ty, Some(&type_name), &mut returned);
831 if detect_receiver_kind(&method.sig) != MethodReceiverKind::None {
832 returned.remove(&type_name);
833 }
834 data.returned_types.extend(returned);
835 }
836 }
837 None => { }
838 }
839 }
840 }
841 Item::Mod(item_mod) => {
842 if let Some((_, child_items)) = &item_mod.content {
843 collect_items_recursive(child_items, data);
845 } else {
846 let mod_name = item_mod.ident.to_string();
848
849 let cfgs = extract_cfg_attrs(&item_mod.attrs);
851 if !cfgs.is_empty() {
852 data.mod_decl_cfgs.insert(mod_name.clone(), cfgs);
853 }
854
855 let path_attr = extract_path_attr(&item_mod.attrs);
857 if let Some(file_path) = path_attr {
858 data.path_redirected_mods.push((mod_name, file_path));
859 } else {
860 data.declared_child_mods.push(mod_name);
861 }
862 }
863 }
864 _ => {}
865 }
866 }
867}
868
869const RF_ERROR_PATTERNS: &[&str] = &[
871 "Rf_error(",
872 "Rf_error_unchecked(",
873 "Rf_errorcall(",
874 "Rf_errorcall_unchecked(",
875];
876
877fn is_suppressed(lines: &[&str], line_idx: usize, code: &str) -> bool {
879 if line_has_allow(lines[line_idx], code) {
880 return true;
881 }
882 if line_idx > 0 && line_has_allow(lines[line_idx - 1], code) {
883 return true;
884 }
885 false
886}
887
888fn line_has_allow(line: &str, code: &str) -> bool {
890 const PREFIX: &str = "// mxl::allow(";
891 if let Some(pos) = line.find(PREFIX) {
892 let after = &line[pos + PREFIX.len()..];
893 if let Some(end) = after.find(')') {
894 let codes = &after[..end];
895 return codes.split(',').any(|c| c.trim() == code);
896 }
897 }
898 false
899}
900
901fn header_allows(lines: &[&str], first_line: usize, line: usize, code: &str) -> bool {
905 (first_line.saturating_sub(1).max(1)..=line).any(|n| {
906 lines
907 .get(n - 1)
908 .is_some_and(|text| line_has_allow(text, code))
909 })
910}
911
912fn collect_returned_types(ty: &syn::Type, self_type: Option<&str>, out: &mut HashSet<String>) {
917 let syn::Type::Path(path) = ty else {
918 return;
919 };
920 let Some(last) = path.path.segments.last() else {
921 return;
922 };
923 if ["Result", "Option", "ExternalPtr"]
924 .iter()
925 .any(|wrapper| last.ident == wrapper)
926 {
927 if let syn::PathArguments::AngleBracketed(args) = &last.arguments
928 && let Some(syn::GenericArgument::Type(inner)) = args.args.first()
929 {
930 collect_returned_types(inner, self_type, out);
931 }
932 } else {
933 out.extend(path_type_name(&path.path, self_type));
934 }
935}
936
937fn path_type_name(path: &syn::Path, self_type: Option<&str>) -> Option<String> {
939 let last = path.segments.last()?;
940 if last.ident == "Self" {
941 self_type.map(str::to_string)
942 } else {
943 Some(last.ident.to_string())
944 }
945}
946
947#[derive(#[automatically_derived]
impl ::core::default::Default for HandBuilt {
#[inline]
fn default() -> Self {
HandBuilt {
types: ::core::default::Default::default(),
self_type: ::core::default::Default::default(),
miniextendr_impl: ::core::default::Default::default(),
locals: ::core::default::Default::default(),
}
}
}Default)]
965struct HandBuilt {
966 types: HashSet<String>,
967 self_type: Option<String>,
969 miniextendr_impl: bool,
971 locals: HashMap<String, String>,
974}
975
976impl HandBuilt {
977 fn in_fn(&mut self, visit_fn: impl FnOnce(&mut Self)) {
979 let locals = std::mem::take(&mut self.locals);
980 visit_fn(self);
981 self.locals = locals;
982 }
983
984 fn literal_type(&self, expr: &syn::Expr) -> Option<String> {
986 let path = match expr {
987 syn::Expr::Struct(literal) => &literal.path,
988 syn::Expr::Call(call) => match &*call.func {
989 syn::Expr::Path(ctor) => &ctor.path,
990 _ => return None,
991 },
992 _ => return None,
993 };
994 path_type_name(path, self.self_type.as_deref())
995 }
996}
997
998impl<'ast> Visit<'ast> for HandBuilt {
999 fn visit_item_impl(&mut self, item: &'ast syn::ItemImpl) {
1000 let self_type = std::mem::replace(&mut self.self_type, impl_type_name(&item.self_ty));
1001 let miniextendr_impl = std::mem::replace(
1002 &mut self.miniextendr_impl,
1003 has_miniextendr_attr(&item.attrs),
1004 );
1005 visit::visit_item_impl(self, item);
1006 self.self_type = self_type;
1007 self.miniextendr_impl = miniextendr_impl;
1008 }
1009
1010 fn visit_item_fn(&mut self, item: &'ast syn::ItemFn) {
1011 self.in_fn(|this| visit::visit_item_fn(this, item));
1012 }
1013
1014 fn visit_trait_item_fn(&mut self, method: &'ast syn::TraitItemFn) {
1015 self.in_fn(|this| visit::visit_trait_item_fn(this, method));
1016 }
1017
1018 fn visit_impl_item_fn(&mut self, method: &'ast syn::ImplItemFn) {
1019 if !self.miniextendr_impl || detect_receiver_kind(&method.sig) == MethodReceiverKind::None {
1020 self.in_fn(|this| visit::visit_impl_item_fn(this, method));
1021 return;
1022 }
1023 let outer = std::mem::take(&mut self.types);
1024 self.in_fn(|this| visit::visit_impl_item_fn(this, method));
1025 let mut built = std::mem::replace(&mut self.types, outer);
1026 if let Some(self_type) = &self.self_type {
1027 built.remove(self_type);
1028 }
1029 self.types.extend(built);
1030 }
1031
1032 fn visit_local(&mut self, local: &'ast syn::Local) {
1033 visit::visit_local(self, local);
1034 let (pat, annotated) = match &local.pat {
1035 syn::Pat::Type(typed) => {
1036 let annotated = match &*typed.ty {
1037 syn::Type::Path(ty) => path_type_name(&ty.path, self.self_type.as_deref()),
1038 _ => None,
1039 };
1040 (&*typed.pat, annotated)
1041 }
1042 pat => (pat, None),
1043 };
1044 let syn::Pat::Ident(binding) = pat else {
1045 return;
1046 };
1047 let name = binding.ident.to_string();
1048 let ty = annotated.or_else(|| {
1049 local
1050 .init
1051 .as_ref()
1052 .and_then(|init| self.literal_type(&init.expr))
1053 });
1054 match ty {
1055 Some(ty) => self.locals.insert(name, ty),
1056 None => self.locals.remove(&name),
1057 };
1058 }
1059
1060 fn visit_fn_arg(&mut self, _: &'ast syn::FnArg) {}
1061
1062 fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) {
1063 if let syn::Expr::Path(func) = &*call.func
1064 && is_external_ptr_new(&func.path)
1065 && let Some(arg) = call.args.first()
1066 {
1067 let built = match arg {
1068 syn::Expr::Path(local) => local
1069 .path
1070 .get_ident()
1071 .and_then(|ident| self.locals.get(&ident.to_string()).cloned()),
1072 literal => self.literal_type(literal),
1073 };
1074 self.types.extend(built);
1075 }
1076 visit::visit_expr_call(self, call);
1077 }
1078
1079 fn visit_path(&mut self, path: &'ast syn::Path) {
1080 for segment in &path.segments {
1081 if segment.ident == "ExternalPtr"
1082 && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
1083 && let Some(syn::GenericArgument::Type(inner)) = args.args.first()
1084 {
1085 collect_returned_types(inner, self.self_type.as_deref(), &mut self.types);
1086 }
1087 }
1088 visit::visit_path(self, path);
1089 }
1090}
1091
1092fn is_external_ptr_new(path: &syn::Path) -> bool {
1095 let mut segments = path.segments.iter().rev();
1096 segments
1097 .next()
1098 .is_some_and(|last| last.ident == "new" || last.ident == "new_unchecked")
1099 && segments
1100 .next()
1101 .is_some_and(|segment| segment.ident == "ExternalPtr")
1102}
1103
1104fn scan_ffi_unchecked_calls(lines: &[&str], data: &mut FileData) {
1106 const PREFIX: &str = "sys::";
1107 for (line_idx, line) in lines.iter().enumerate() {
1108 let trimmed = line.trim();
1109 if trimmed.starts_with("//") {
1110 continue;
1111 }
1112 if trimmed.starts_with("#[") {
1113 continue;
1114 }
1115 let code_part = match trimmed.find("//") {
1117 Some(pos) => &trimmed[..pos],
1118 None => trimmed,
1119 };
1120 let mut search_from = 0;
1121 while let Some(sys_pos) = code_part[search_from..].find(PREFIX) {
1122 let abs_pos = search_from + sys_pos;
1123 let after = &code_part[abs_pos + PREFIX.len()..];
1124 let ident_end = after
1125 .find(|c: char| !c.is_alphanumeric() && c != '_')
1126 .unwrap_or(after.len());
1127 let ident = &after[..ident_end];
1128 if ident.ends_with("_unchecked")
1129 && after[ident_end..].starts_with('(')
1130 && !is_suppressed(lines, line_idx, "MXL301")
1131 {
1132 data.ffi_unchecked_calls
1133 .push((ident.to_string(), line_idx + 1));
1134 }
1135 search_from = abs_pos + PREFIX.len() + ident_end;
1136 }
1137 }
1138}
1139
1140fn extract_return_type_str(output: &syn::ReturnType) -> String {
1146 use quote::ToTokens;
1147 match output {
1148 syn::ReturnType::Default => String::new(),
1149 syn::ReturnType::Type(_, ty) => ty.to_token_stream().to_string(),
1150 }
1151}
1152
1153fn detect_receiver_kind(sig: &syn::Signature) -> MethodReceiverKind {
1158 let first = match sig.inputs.first() {
1159 Some(arg) => arg,
1160 None => return MethodReceiverKind::None,
1161 };
1162 match first {
1163 syn::FnArg::Receiver(recv) => {
1164 if recv.colon_token.is_some() {
1170 match recv.ty.as_ref() {
1172 syn::Type::Reference(r) if r.mutability.is_some() => MethodReceiverKind::RefMut,
1173 syn::Type::Reference(_) => MethodReceiverKind::Ref,
1174 ty => match self_handle_ty(ty) {
1175 Some("ExternalPtr") => MethodReceiverKind::ExternalPtrValue,
1176 Some("ExternalPtrRef") => MethodReceiverKind::ExternalPtrRef,
1177 Some("ExternalPtrMut") => MethodReceiverKind::ExternalPtrMut,
1178 _ => MethodReceiverKind::None,
1179 },
1180 }
1181 } else {
1182 if recv.mutability.is_some() {
1184 MethodReceiverKind::RefMut
1185 } else if recv.reference.is_some() {
1186 MethodReceiverKind::Ref
1187 } else {
1188 MethodReceiverKind::Value
1189 }
1190 }
1191 }
1192 syn::FnArg::Typed(_) => {
1193 MethodReceiverKind::None
1196 }
1197 }
1198}
1199
1200fn self_handle_ty(ty: &syn::Type) -> Option<&'static str> {
1204 let syn::Type::Path(p) = ty else {
1205 return None;
1206 };
1207 let last = p.path.segments.last()?;
1208 let name = ["ExternalPtr", "ExternalPtrRef", "ExternalPtrMut"]
1209 .into_iter()
1210 .find(|name| last.ident == name)?;
1211 let syn::PathArguments::AngleBracketed(ref args) = last.arguments else {
1212 return None;
1213 };
1214 args.args
1215 .iter()
1216 .any(|arg| {
1217 #[allow(non_exhaustive_omitted_patterns)] match arg {
syn::GenericArgument::Type(syn::Type::Path(tp)) if
tp.path.is_ident("Self") => true,
_ => false,
}matches!(arg, syn::GenericArgument::Type(syn::Type::Path(tp)) if tp.path.is_ident("Self"))
1218 })
1219 .then_some(name)
1220}
1221
1222fn has_constructor_attr(attrs: &[syn::Attribute]) -> bool {
1225 for attr in attrs {
1226 if attr
1227 .path()
1228 .segments
1229 .last()
1230 .is_none_or(|seg| seg.ident != "miniextendr")
1231 {
1232 continue;
1233 }
1234 if let syn::Meta::List(meta_list) = &attr.meta {
1235 let tokens = meta_list.tokens.to_string();
1236 if tokens
1238 .split(|c: char| !c.is_alphanumeric() && c != '_')
1239 .any(|t| t == "constructor")
1240 {
1241 return true;
1242 }
1243 }
1244 }
1245 false
1246}
1247
1248fn scan_rf_error_calls(lines: &[&str], data: &mut FileData) {
1252 for (line_idx, line) in lines.iter().enumerate() {
1253 let trimmed = line.trim();
1254 if trimmed.starts_with("//") {
1255 continue;
1256 }
1257 let code_part = match trimmed.find("//") {
1259 Some(pos) => &trimmed[..pos],
1260 None => trimmed,
1261 };
1262 for pattern in RF_ERROR_PATTERNS {
1263 if code_part.contains(pattern) && !is_suppressed(lines, line_idx, "MXL300") {
1264 let fn_name = &pattern[..pattern.len() - 1];
1265 data.rf_error_calls
1266 .push((fn_name.to_string(), line_idx + 1));
1267 }
1268 }
1269 }
1270}
1271
1272const NONAPI_ATTRIB_FNS: &[&str] = &["ATTRIB", "SET_ATTRIB"];
1276
1277fn scan_nonapi_attrib_calls(lines: &[&str], data: &mut FileData) {
1286 for (line_idx, line) in lines.iter().enumerate() {
1287 let trimmed = line.trim();
1288 if trimmed.starts_with("//") {
1289 continue;
1290 }
1291 let code_part = match trimmed.find("//") {
1293 Some(pos) => &trimmed[..pos],
1294 None => trimmed,
1295 };
1296 for ident in called_idents(code_part) {
1297 let base = ident.strip_suffix("_unchecked").unwrap_or(ident);
1298 if NONAPI_ATTRIB_FNS.contains(&base) && !is_suppressed(lines, line_idx, "MXL304") {
1299 data.nonapi_attrib_calls
1300 .push((ident.to_string(), line_idx + 1));
1301 }
1302 }
1303 }
1304}
1305
1306fn called_idents(code: &str) -> Vec<&str> {
1310 let is_ident_char = |c: char| c.is_alphanumeric() || c == '_';
1311 let mut idents = Vec::new();
1312 let mut chars = code.char_indices().peekable();
1313 while let Some((start, c)) = chars.next() {
1314 if !is_ident_char(c) {
1315 continue;
1316 }
1317 let mut end = start + c.len_utf8();
1318 while let Some(&(idx, next)) = chars.peek() {
1319 if !is_ident_char(next) {
1320 break;
1321 }
1322 end = idx + next.len_utf8();
1323 chars.next();
1324 }
1325 if code[end..].trim_start().starts_with('(') {
1326 idents.push(&code[start..end]);
1327 }
1328 }
1329 idents
1330}
1331
1332const RAW_R_EVAL_FNS: &[&str] = &[
1339 "Rf_eval",
1340 "R_forceAndCall",
1341 "Rf_applyClosure",
1342 "R_CheckUserInterrupt",
1343];
1344
1345fn block_lines(block: &syn::Block) -> (usize, usize) {
1347 let span = &block.brace_token.span;
1348 (span.open().start().line, span.close().end().line)
1349}
1350
1351fn scan_raw_r_eval_calls(lines: &[&str], data: &mut FileData) {
1370 let mut in_body = ::alloc::vec::from_elem(false, lines.len())vec![false; lines.len()];
1371 for &(open, close) in &data.miniextendr_bodies {
1372 for flag in in_body.iter_mut().take(close).skip(open - 1) {
1373 *flag = true;
1374 }
1375 }
1376 for (line_idx, line) in lines.iter().enumerate() {
1377 if !in_body[line_idx] {
1378 continue;
1379 }
1380 let trimmed = line.trim();
1381 if trimmed.starts_with("//") {
1382 continue;
1383 }
1384 let code_part = match trimmed.find("//") {
1386 Some(pos) => &trimmed[..pos],
1387 None => trimmed,
1388 };
1389 for ident in called_idents(code_part) {
1390 let base = ident.strip_suffix("_unchecked").unwrap_or(ident);
1391 if RAW_R_EVAL_FNS.contains(&base) && !is_suppressed(lines, line_idx, "MXL305") {
1392 data.raw_r_eval_calls
1393 .push((ident.to_string(), line_idx + 1));
1394 }
1395 }
1396 }
1397}
1398
1399const INTO_SEXP_CALLS: &[&str] = &["into_sexp(", "into_sexp_unchecked("];
1405
1406fn scan_vec_into_sexp_calls(lines: &[&str], data: &mut FileData) {
1443 let mut literal_depth: i32 = 0;
1446 let mut element_protected = false;
1451
1452 for (line_idx, line) in lines.iter().enumerate() {
1453 let trimmed = line.trim();
1454 if trimmed.starts_with("//") {
1455 continue;
1456 }
1457 let code_part = match line.find("//") {
1459 Some(pos) => &line[..pos],
1460 None => *line,
1461 };
1462
1463 let bytes = code_part.as_bytes();
1464 let mut i = 0;
1465 while i < bytes.len() {
1466 if bytes[i] == b'[' {
1468 let opens_literal =
1471 bytes[..i].ends_with(b"vec!") || (i > 0 && bytes[i - 1] == b'&');
1472 if opens_literal || literal_depth > 0 {
1473 literal_depth += 1;
1475 element_protected = false;
1476 }
1477 i += 1;
1478 continue;
1479 }
1480 if bytes[i] == b']' {
1481 if literal_depth > 0 {
1482 literal_depth -= 1;
1483 }
1484 i += 1;
1485 continue;
1486 }
1487 if literal_depth > 0 {
1488 if bytes[i] == b',' {
1490 element_protected = false;
1491 i += 1;
1492 continue;
1493 }
1494 if bytes[i..].starts_with(b"protect_raw(")
1497 || bytes[i..].starts_with(b"protect(")
1498 || bytes[i..].starts_with(b"protect_with_index(")
1499 {
1500 element_protected = true;
1501 }
1502 for pattern in INTO_SEXP_CALLS {
1504 if bytes[i..].starts_with(pattern.as_bytes())
1505 && !element_protected
1506 && !is_suppressed(lines, line_idx, "MXL302")
1507 {
1508 let call_name = &pattern[..pattern.len() - 1];
1509 data.vec_into_sexp_calls
1510 .push((call_name.to_string(), line_idx + 1));
1511 }
1512 }
1513 }
1514 i += 1;
1515 }
1516 }
1517}
1518