Skip to main content

miniextendr_lint/rules/
no_constructor.rs

1//! MXL121: a class R code can never create.
2//!
3//! A type's objects come from its constructor, from the functions that
4//! return one, or from pointers the crate builds itself. The constructor is
5//! the method marked `#[miniextendr(<system>(constructor))]`, else the
6//! receiver-less method returning bare `Self` the macro infers, in any of the
7//! type's inherent `#[miniextendr]` impl blocks. A factory is any
8//! `#[miniextendr]` fn or method returning `T`, `Self` or `ExternalPtr<T>`,
9//! bare or inside `Result` / `Option` (`returned_types` in the index). A
10//! hand-built pointer is an `ExternalPtr::new(T { .. })` / `ExternalPtr::new(T(..))`,
11//! also through a local bound earlier in the same function body
12//! (`let x = T { .. }`, `let x = T(..)`, `let x: T = ..`), or an
13//! `ExternalPtr<T>` written as a type or turbofish, anywhere in the crate,
14//! `#[miniextendr]` or not (`hand_built_types`): a wrapper whose hand-written
15//! `IntoR` builds the pointer is a factory too. Without any of these, every
16//! method of the class needs an object R code has no way to get.
17//!
18//! The type's own methods taking `self` don't count: they need an object
19//! already. Nor do parameters: the caller has the object. vctrs classes are
20//! skipped: the package builds their vectors in R from the payload, not from
21//! a `T`. `ExternalPtr::new(value)` of a value from a call whose type isn't
22//! written (`let record = make_record(inner); ExternalPtr::new(record)`), or
23//! of a value from another function, is invisible; writing the type
24//! (`let record: T = ..`) or `// mxl::allow(MXL121)` covers it. So is code
25//! inside macro calls (`vec![..]`), which `syn` does not parse.
26
27use std::collections::{BTreeMap, HashSet};
28use std::path::PathBuf;
29
30use crate::crate_index::{CrateIndex, InherentImpl};
31use crate::diagnostic::Diagnostic;
32use crate::lint_code::LintCode;
33
34pub fn check(index: &CrateIndex, diagnostics: &mut Vec<Diagnostic>) {
35    let mut returned: HashSet<&str> = HashSet::new();
36    let mut marked: HashSet<&str> = HashSet::new();
37    let mut blocks: BTreeMap<&str, Vec<(&PathBuf, &InherentImpl)>> = BTreeMap::new();
38    for (path, data) in &index.file_data {
39        returned.extend(data.returned_types.iter().map(String::as_str));
40        returned.extend(data.hand_built_types.iter().map(String::as_str));
41        for (type_name, methods) in &data.impl_methods {
42            if methods.iter().any(|m| m.has_constructor_attr) {
43                marked.insert(type_name);
44            }
45        }
46        for block in &data.inherent_impls {
47            blocks
48                .entry(&block.type_name)
49                .or_default()
50                .push((path, block));
51        }
52    }
53
54    for (type_name, blocks) in blocks {
55        if returned.contains(type_name)
56            || marked.contains(type_name)
57            || blocks
58                .iter()
59                .any(|(_, b)| b.class_system == "vctrs" || b.allows_mxl121)
60        {
61            continue;
62        }
63        let Some((path, first)) = blocks.iter().min_by_key(|(path, b)| (*path, b.line)) else {
64            continue;
65        };
66        diagnostics.push(
67            Diagnostic::new(
68                LintCode::MXL121,
69                path.as_path(),
70                first.line,
71                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("R code can never create a `{0}`: no impl block of `{0}` has a constructor, no `#[miniextendr]` function or method returns one, and the crate builds no `ExternalPtr<{0}>`",
                type_name))
    })format!(
72                    "R code can never create a `{type_name}`: no impl block of `{type_name}` \
73                     has a constructor, no `#[miniextendr]` function or method returns one, \
74                     and the crate builds no `ExternalPtr<{type_name}>`"
75                ),
76            )
77            .with_help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("mark a constructor with `#[miniextendr(<system>(constructor))]`, or add a `#[miniextendr]` function returning `{0}` or `ExternalPtr<{0}>`; where the crate builds the pointer from a value from a call whose type isn\'t written, write it (`let value: {0} = ...`) or put `// mxl::allow(MXL121)` on the impl",
                type_name))
    })format!(
78                "mark a constructor with `#[miniextendr(<system>(constructor))]`, or add a \
79                 `#[miniextendr]` function returning `{type_name}` or `ExternalPtr<{type_name}>`; \
80                 where the crate builds the pointer from a value from a call whose type \
81                 isn't written, write it (`let value: {type_name} = ...`) or put \
82                 `// mxl::allow(MXL121)` on the impl"
83            )),
84        );
85    }
86}