Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

My feedback for 0.1.1 #189

Closed
Veetaha opened this issue Jul 18, 2023 · 9 comments
Closed

My feedback for 0.1.1 #189

Veetaha opened this issue Jul 18, 2023 · 9 comments
Labels
C-feedback Category: Feedback

Comments

@Veetaha
Copy link
Contributor

Veetaha commented Jul 18, 2023

First of all, that's a great start! Writing custom lints feels rather nice for me, and doesn't need to depend on nightly at least from the interface side.

I used the 0.1.1 tooling to implement a lint that suggests using into_owned() instead of to_string() for Cow<'_, str>.

Here was my code:

Details
use marker_api::ast::generic::SemGenericArgKind;
use marker_api::ast::ty::SemTyKind;
use marker_api::prelude::*;
use marker_api::{LintPass, LintPassInfo, LintPassInfoBuilder};

#[derive(Default)]
struct ToStringOnCowStrPass {}
marker_api::export_lint_pass!(ToStringOnCowStrPass);

marker_api::declare_lint! {
    /// # What it does
    /// This lint detects the usage of `.to_string()` on a `Cow<str>`.
    ///
    /// # Example
    /// ```rs
    /// PathBuf::from("foo").to_string_lossy().to_string()
    /// ```
    ///
    /// Use instead:
    /// ```rs
    /// PathBuf::from("foo").to_string_lossy().into_owned()
    /// ```
    ///
    /// If you intended to clone the `Cow`, then use `.clone()` or `.to_owned()`.
    TO_STRING_ON_COW_STR,
    Warn,
}

impl LintPass for ToStringOnCowStrPass {
    fn info(&self) -> LintPassInfo {
        LintPassInfoBuilder::new(Box::new([TO_STRING_ON_COW_STR])).build()
    }

    fn check_expr<'ast>(
        &mut self,
        ctx: &'ast AstContext<'ast>,
        expr: marker_api::ast::expr::ExprKind<'ast>,
    ) {
        let ExprKind::Method(method) = expr else {
            return;
        };

        if method.method().ident().name() != "to_string" {
            return;
        }

        let SemTyKind::Adt(reciever) = method.receiver().ty() else {
            return;
        };

        if !ctx
            .resolve_ty_ids("std::borrow::Cow")
            .contains(&reciever.def_id())
        {
            return;
        }

        let [SemGenericArgKind::Ty(str)] = reciever.generics().args() else {
            return;
        };

        if !matches!(str, SemTyKind::Text(str) if str.is_str()) {
            return;
        }

        ctx.emit_lint(
            TO_STRING_ON_COW_STR,
            method.receiver().id(),
            "replace this with .into_owned()",
            method.method().ident().span(),
            |_| {},
        );
    }
}

And it produced a clippy-style error output, which is awesome:

 --> crates/scratch/src/main.rs:8:19
  |
8 |     let val = cow.to_string();
  |                   ^^^^^^^^^
  |
  = note: `#[warn(marker::to_string_on_cow_str)]` on by default

When running cargo marker (with rust-toolchain file removed (#188)) on my huge private repo of hundreds of crates cargo marker didn't choke, however, it spitted millions of warnings about the usage of async functions and blocks. And my custom to_string_on_cow_str lint also threw the warnings in the places where I expected them. So, currently, the lack of support for async is the most obvious blocker for me.

The problems that I saw when using marker.

Feedback loop

I had to run cargo clean each time I change the lint and want to check how it works. My feedback loop was:

  1. Change the custom lint's code.
  2. Run cargo clean
  3. Run cargo marker

If I omit the step 2 I still see the output from the old version of my lint code.

I remember there was a different problem in cargo-clippy a long ago, which was also worked around with cargo clean to bust the incremental compilation cache (rust-lang/rust-clippy#2604 (comment)). It isn't what happens here, but I hope marker won't have the same caveat.

marker_api

export_lint_pass could be a derive macro, or an attribute macro, which would reduce the boilerplate.

It's not very clear how lints and lint passes should interact. Should I try to fit all lints in a single pass, or is it fine to have a single lint for a single pass struct? If yes, then maybe the code for a single lint and a single pass could be simplified? For example, the need to return the slice of lints in info method feels inconvenient. Could marker somehow benefit from the inventory crate to avoid that?

Lifetime parameters

There is no info about the lifetimes passed in generic args. For example, I expected that Cow's generic parameters would contain the lifetime parameter as their first argument, but there are only type arguments. Are there plans to provide access to lifetime parameters info in the AST?

Comments

There is no way to lint code comments. I, for example, would like to enforce some conventions around TODO/FIXME comments and their format. Are there plans to provide access to code comments (not doc comments but regular comments) in the AST?

Macros

I would like to be able to lint the callsites of declarative macros. For example detect code like this: tracing::error!(?err, "Error"). Is it possible to see unexpanded macros? Will there be a concept of an early lint pass?

emit_lint

The emit_lint method is the central method and requires documentation. It is not clear to me what the node: impl Into<EmissionNode> means. Is this used to somehow mark the place where the user may put an #[allow(marker::lint_name)] to ignore the lint? How does the EmissionNode interact with the span?

The 5-th argument to emit_lint will not be used in 90% of cases, as to my naive first look. It's better to provide the most succinct API for general usage. I don't like that emit_node takes 5 arguments (that's too much). Ideally, there could be several methods for emitting lints with different levels of complexity. E.g. emit_lint(lint, msg, span), emit_lint_with_decor(lint, msg, span, decor_fn), etc. but it would be much better to have a builder API (cc typed_builder or buildstructor crates for that, or handroll your own) for emitting the lint:

ctx.build_lint(LINT_NAME)
    .message("this code is wrong")
    .span(span)
    .decor(|_| { /* do smth custom here, setting .decor() is optional */})
    .emit()

A similar comment applies to resolve_ty_ids().contains() pattern. I think this is going to be repetitive, and it deserves a method e.g. .is_type(str, type_id).

Ignoring the lints

One of the main blocker for using marker in production for me is the inability to ignore the lints. I can't put #[allow(marker::lint_name)] anywhere in my code because I get this:

unknown tool name `marker` found in scoped lint: `marker::to_string_on_cow_str`
 --> crates/scratch/src/main.rs:8:13
  |
8 |     #[allow(marker::to_string_on_cow_str)]
  |             ^^^^^^

Unfortunately, the custom tool name feature is not stable in Rust, and I am not sure how ignoring the lints may then be used in stable. Maybe having a syntax with comments e.g. // @allow marker::to_string_on_cow_str could be used as a workaround? Do you have ideas on how this can be done in stable, or we will need to wait until custom tools are stabilized in Rust?

Errors

I guess it's obvious that error output in cargo-marker is far from ideal. The error messages and context when something is messed up in configs need improvements. It's very easy to mess something up in Cargo.toml and get a raw NoTargetDir output.
In general, I recommend using libraries for logging and error handling such as:

Other questions

I skimmed through the code of cargo marker and found out it uses clap's builder API. I wonder why not to use its derive attributes? They are much more convenient because they provide strong typing, type safety and the lack of occasional dynamic invariants in the code.

@Centri3
Copy link

Centri3 commented Jul 18, 2023

Comments

There is no way to lint code comments. I, for example, would like to enforce some conventions around TODO/FIXME comments and their format. Are there plans to provide access to code comments (not doc comments but regular comments) in the AST?

Comments are unfortunately not in the AST or HIR, so everything would need to be relexed for comments to be available. There's an AST utils function gather_comments in rustc which could perhaps be exposed in marker

emit_lint

I believe clippy-like utils functions are planned, like span_lint_and_sugg :) I could probably create a PR to quickly add them, but a builder method could be nice as well

afaik, node in particular is a node in the AST; think of it like an expression, statement, item, etc. This determines what lint attributes (allow, warn, etc) are taken into account, whilst span is the, well, span. It could probably only optionally take the span and use the node's span instead in the future

I had to run cargo clean each time I change the lint and want to check how it works. My feedback loop was:

Interesting, for me just cargo bless/cargo uitest worked; unless you're not using uitests, in which case that makes sense (and when developing lints you usually want to run it against a test anyway, before on actual crates)

I skimmed through the code of cargo marker and found out it uses clap's builder API. I wonder why not to use its derive attributes? They are much more convenient because they provide strong typing, type safety and the lack of occasional dynamic invariants in the code.

derive is slower to compile, if I had to guess.

@xFrednet
Copy link
Member

First of all, that's a great start! [...]
on my huge private repo of hundreds of crates cargo marker didn't choke

Thank you very much. This feedback is super valuable! ❤️

however, it spitted millions of warnings about the usage of async functions and blocks. [...] So, currently, the lack of support for async is the most obvious blocker for me.

You can disable the warning by setting the MARKER_DISABLE_ASYNC_WARNING environment value. Maybe only emitting one warning (per crate) would be sufficient. The progress for these expressions is tracked in #174

And my custom to_string_on_cow_str lint also threw the warnings in the places where I expected them.

That's awesome!! I'm currently starting to collect a few example lints in the https://github.com/rust-marker/marker-example-lints repo. Would it be alright if that lint was added to the repo?

Feedback loop

I had to run cargo clean each time I change the lint and want to check how it works. My feedback loop was:

That's a good point, the caching check currently doesn't consider that path lint crates can change between runs. Nice catch! Just created an issue for this: #190

marker_api

It's not very clear how lints and lint passes should interact. Should I try to fit all lints in a single pass, or is it fine to have a single lint for a single pass struct?

Every lint crate can only have one lint pass, the crate should then distribute the control flow internally. So, it's one lint pass for all lints. There is no proven structure for this yet, and something worth investigating.

You mentioned the inventory crate earlier, it's something I will take a look at :)

Lifetime parameters

There is no info about the lifetimes passed in generic args. [...] Are there plans to provide access to lifetime parameters info in the AST?

Syntactic lifetimes, ie ones written by the user, should be available. Currently, I'm not planning to include the semantic ones, for a few reasons:

  1. It feels like the semantic analysis of lifetimes is still evolving with editions.
  2. Rustc is the only driver currently capable of checking lifetimes AFAIK.

For me, the main question is: how much they are needed? Very few lints in Clippy require this information. Maybe it would be sufficient to provide a simple representation for semantic generics, without the entire lifetime analysis. 🤔

Comments

There is no way to lint code comments. I, for example, would like to enforce some conventions around TODO/FIXME comments and their format. Are there plans to provide access to code comments (not doc comments but regular comments) in the AST?

In addition to what @Centri3 said: Comments are a bit weird, since they don't follow the normal AST structure, they can basically show up wherever they want to. Someone wrote a related block regarding comments in JavaParser: https://tomassetti.me/java-comments-parsing/ . The basic problem was deciding where they belong to.

There are no direct plans to include them, but also no decision to exclude them.

Macros

I would like to be able to lint the callsites of declarative macros. For example detect code like this: tracing::error!(?err, "Error"). Is it possible to see unexpanded macros? Will there be a concept of an early lint pass?

Currently, there is no concept for that, but I'm hoping to add something for that. However, my current plan will also require some support from rustc, so it'll take time, and it's not the highest prio.

emit_lint

The emit_lint method is the central method and requires documentation. It is not clear to me what the node: impl Into<EmissionNode> means. Is this used to somehow mark the place where the user may put an #[allow(marker::lint_name)] to ignore the lint? How does the EmissionNode interact with the span?

Yeah, it's used to determine the lint level (and fulfill lint expectations once RFC-2383 is stabilized). Good point that the documentation is missing 👍

I believe clippy-like utils functions are planned, like span_lint_and_sugg :) I could probably create a PR to quickly add them, but a builder method could be nice as well. (From @Centri3)

In Clippy, there was a discussion to potentially move a way from those, for two reasons:

  1. Currently, there are several ways to emit lints, and it's not always clear which one is the correct one
  2. Constructing the suggestions and additional notes can take time and caused several panics in Clippy in the past. By having them in a closure, we can make sure that the code in question is only called if the lint will actually be emitted to the user.

The builder variant could be interesting. I don't like the required .emit() call, but there are other workarounds. I'll have to think about this one for a bit.

A similar comment applies to resolve_ty_ids().contains() pattern. I think this is going to be repetitive, and it deserves a method e.g. .is_type(str, type_id).

Something like this is planned to be provided by marker_utils 👍

Ignoring the lints

One of the main blocker for using marker in production for me is the inability to ignore the lints. I can't put #[allow(marker::lint_name)] anywhere in my code because I get this:

unknown tool name `marker` found in scoped lint: `marker::to_string_on_cow_str`
 --> crates/scratch/src/main.rs:8:13
  |
8 |     #[allow(marker::to_string_on_cow_str)]
  |             ^^^^^^

Unfortunately, the custom tool name feature is not stable in Rust, and I am not sure how ignoring the lints may then be used in stable. Maybe having a syntax with comments e.g. // @allow marker::to_string_on_cow_str could be used as a workaround? Do you have ideas on how this can be done in stable, or we will need to wait until custom tools are stabilized in Rust?

Yeah, this is a real limitation and it sounds like there is no plan to stabilize custom tool names anytime soon (See: rust-lang/rust#66079). Once Marker is more grown up, I hope to change the error to a new lint in rustc. But that's just an idea, which will require discussions with the compiler team.

For now, the best way is to have a marker feature with a cfg_attr, like this:

#[cfg_attr(marker, allow(marker::duck_lint))]

Errors

I guess it's obvious that error output in cargo-marker is far from ideal. The error messages and context when something is messed up in configs need improvements. It's very easy to mess something up in Cargo.toml and get a raw NoTargetDir output. In general, I recommend using libraries for logging and error handling such as:

* [`tracing`](https://docs.rs/tracing/latest/tracing/)

* [`thiserror`](https://docs.rs/thiserror)

* [`miette`](https://docs.rs/miette)

Absolutely true, I'm hoping to improve this for the next version.

cc: #169

Other questions

I skimmed through the code of cargo marker and found out it uses clap's builder API. I wonder why not to use its derive attributes? They are much more convenient because they provide strong typing, type safety and the lack of occasional dynamic invariants in the code.

This mostly comes down to personal preference or inexperience. cargo_marker is my first bigger CLI. The builder API felt a bit easier to use, but it requires checking for types etc. Any refactorings would be welcome 😉


Thank you for this detailed feedback, this was exactly what I was hoping for! You touched on a few lints that you would like to implement. If you have the time, could you maybe create a User Story for them?

@xFrednet xFrednet added the C-discussion Category: Discussion / Design decision. See also rust-marker/design label Jul 18, 2023
@xFrednet xFrednet added C-feedback Category: Feedback and removed C-discussion Category: Discussion / Design decision. See also rust-marker/design labels Jul 26, 2023
@Veetaha
Copy link
Contributor Author

Veetaha commented Jul 26, 2023

@xFrednet I tried everything I could think of, but #[allow(...)] doesn't work in any possible way. Here was my last non-working try

#![feature(register_tool)]
#![register_tool(marker)]

fn main() {
    let path = std::path::Path::new("foo.txt");

    #[allow(marker::to_string_on_cow_str)]
    let _ = path.to_string_lossy().to_string();
}

(btw I'd really like to avoid using the two attributes at the top if possible).

The lint is not ignored, it's always reported when I run cargo marker

@xFrednet
Copy link
Member

Hmmm, now that you mention it, I'm unsure if Marker actually registers the lint with rustc... This would mean that the lint level is not tracked right now... I will investigate this, thank you for pointing that out!

Once the issue is fixed, there would be two options:

  1. I'm hoping to convince the compiler team that unknown tool lints should be a warning instead of an error. This would still not be ideal, but at least allow users to just add #![allow(unknown_linter)] to their crate root instead of requiring nightly with the register_tool feature
  2. Dylint suggest using conditional compilation, that should work, once lint levels are tracked correctly. It's still not that nice IMO

@Veetaha
Copy link
Contributor Author

Veetaha commented Jul 26, 2023

@xFrednet I think if marker provides a way to make #[cfg_attr(...)] work, this will unblock its production readiness for me. There should be at least some way to ignore lints at least on statement/function level.

Having a single-line #[cfg_attr(...)] seems like a good solution to me given the today's status quo of register_tool feature. Also, even having to add #[register_tool] at the top of the crate root file if it were stable seems cumbersome 🤔.

So... if marker passed a custom cfg flag to the compilation process allowing us to use cfg_attr that would get marker on the rails very quickly.

@xFrednet
Copy link
Member

Sounds good, I'll try to look into this, until v0.2.0 and also document how to best handle this. It sounds like the perfect topic, for the start of The Marker Book ^^

I've extracted these points into separate separate issues #200 and #201

@Veetaha
Copy link
Contributor Author

Veetaha commented Jul 27, 2023

@xFrednet Regarding the points in #189 (comment).

Would it be alright if that lint was added to the repo?

Yes, sure.

Every lint crate can only have one lint pass, the crate should then distribute the control flow internally. So, it's one lint pass for all lints. There is no proven structure for this yet, and something worth investigating.

It makes sense to describe the best practices for organizing a set of lints. Maybe something like "lint guidelines" for different use cases. But the best would be if marker could provide a framework for organizing lints to enforce consistent lint development flows, structure, and release process to have something standard established and relieve people from having to go through the thinking/research process described below.

  • If a single lint pass can be defined in a single crate, then what will happen if I define several lint passes in the crate? Will this fail to compile with cryptic linker errors? Will this compile but produce UB? If this doesn't produce UB will only one random lint pass be selected?
  • Given a single lint pass can be defined per crate, then what is the best way to organize the code for multiple lints inside of a single crate? Each lint should be isolated from each other to prevent mixing their business logic. What is the standard and cleanest way to achieve that isolation given they all need to be part of the same lint pass trait implementation? I'd like the process of adding a lint to be as standardized as possible say "copy this example lint file, paste it, add mod declaration and write the lint logic in that file in isolation".
  • When can I know it's time to create a second lint crate; what should I consider when making such a decision? Should I just always have a single lint crate for the project that I want to have custom lints for? Maybe some reusable lints need to be extracted into separate crates to reuse them in multiple repos?
  • If there are multiple lints in a crate, but I want to enable just a few of them, then how can I configure marker to do that? Doesn't it mean that to be able to use only part of the lints from the ones defined in a single crate that crate needs to be split into several ones?
  • Are there any performance implications in having several lints in a single crate or in multiple ones (meaning in different lint pass implementations)?

Someone wrote a related block regarding comments in JavaParser: tomassetti.me/java-comments-parsing . The basic problem was deciding where they belong to.

I think it is just not important where the comments are attached to. I know from rust-analyzer experience, that RA has a lossless syntax tree and comments are easily accessible via the AST in its internal code. You can inspect how comments are nested in its syntax tree by using this command in VSCode:
image

This shows the AST representation of the Rust file where you can see what AST nodes the comments are attached to.

image

In a nutshell, there isn't a strict single "right way" to put comments in the AST. Rust analyzer prefers nesting them under the item that follows the comment. For example, if there is a comment preceding a function or variable declaration, then it's nested under that item's AST node. The only thing is that this must be documented to let users of marker make correct expectations about what nodes they should search comments in.

Maybe it would be sufficient to provide a simple representation for semantic generics, without the entire lifetime analysis.

I am not sure what exact differencies you imply between syntactic and semantic lifetimes. Users will definitely not need any results of borrow-checking analysis. I think the following set of operations should cover the use cases.

  • Get the names of lifetime parameters
  • Get the declaration place of the lifetime parameter from its usage
  • Check if the lifetime is a named lifetime parameter, or an anonymous lifetime parameter, or a 'static lifetime
  • Get the lifetime parameter bounds e.g. <'a: 'b, 'b> (I think this one is approaching the max info users will need from lifetimes at all)

Linting the lifetimes usages on its own is definitely a very narrow and small area, so it's not worth spending too much time here. My comment was basically about the expectation to see at least some lifetime info in the generics of the Cow<'_, str> type, but I found that my lint sees it as just Cow<str> not knowing that this is a borrowed type at all. Having some minimum lifetime context described in the bullet list above may fix the intuitiveness of answering the questions for the lint implementor like "I know Cow has a lifetime parameter. Do I need to skip the first generic arg of that type if I want to ignore it?"

You touched on a few lints that you would like to implement. If you have the time, could you maybe create a User Story for them?

Sure will write them up when feeling inspired.

Followup questions

What will happen if the lint does something stupid in the LintPass impl?

  • Panics
  • Calls std::process::exit()
  • Calls std::process::abort()
  • Blocks the thread with thread::sleep or expensive computations or event enters an infinite loop
  • Stores things in global static mutable variables guarded by some synchronization primitive
  • Stores things in global thread_local mutable variables. Basically, what is the thread interaction environment here?
  • Uses logging macros from tracing/log crates or even bare [e]print[ln]!()?
  • Spawns a new thread or even spins an async runtime

@xFrednet
Copy link
Member

It makes sense to describe the best practices for organizing a set of lints. Maybe something like "lint guidelines" for different use cases. But the best would be if marker could provide a framework for organizing lints to enforce consistent lint development flows, structure, and release process to have something standard established and relieve people from having to go through the thinking/research process described below.

  • If a single lint pass can be defined in a single crate, then what will happen if I define several lint passes in the crate? Will this fail to compile with cryptic linker errors? Will this compile but produce UB? If this doesn't produce UB will only one random lint pass be selected?

TBH, IDK. The export_lint_pass macro declares some functions with #[no_mangle]. I believe the code would compile, but only one lint pass would be found, so a random one would be called. I plan to add a lint for this to marker_lints (#207)

  • Given a single lint pass can be defined per crate, then what is the best way to organize the code for multiple lints inside of a single crate? Each lint should be isolated from each other to prevent mixing their business logic. What is the standard and cleanest way to achieve that isolation given they all need to be part of the same lint pass trait implementation? I'd like the process of adding a lint to be as standardized as possible say "copy this example lint file, paste it, add mod declaration and write the lint logic in that file in isolation".

Currently, I would copy a structure similar to Clippy. The structure would be:

  1. Register the lint pass in lib.rs
  2. Each lint has its own module (in a separate file). (This is not quite true, if there are several lints which lint almost the same AST pattern, they should be combined)
  3. The lib.rs file forwards the check_* call.

This is the best I can come up with for now, until we have more crates that build on top of Marker's API. This structure is also used in marker-example-lints.

  • When can I know it's time to create a second lint crate; what should I consider when making such a decision? Should I just always have a single lint crate for the project that I want to have custom lints for? Maybe some reusable lints need to be extracted into separate crates to reuse them in multiple repos?

I would split lint crates based on the author/target. For example:

  • Each dependency should only define one lint crate.
  • Lints only used for internal checks/validation should be in a separate lint crate, from the public ones mentioned above.
  • If some lints are performance intensive or architecture checks, they should be in a separate lint crate, that only runs in the CI.

Here is an example:

  • marker as a dependency would define marker_lints, for users of the library. That crate lints for marker_api, marker_utils, marker_uitest etc
  • Internal lints, are in a separate crate, maybe called marker_internal_lints. This might check node naming conventions in the marker_api crate
  • Architecture checks, like data flow analysis, would be in a separate crate that only runs in the CI. Maybe marker_arch_lints or so. It could also be a feature flag inside the marker_internal_lints crate.
  • If there are multiple lints in a crate, but I want to enable just a few of them, then how can I configure marker to do that? Doesn't it mean that to be able to use only part of the lints from the ones defined in a single crate that crate needs to be split into several ones?

Lint levels can be configured individually, like normal rustc lints. The lint crate authors have to make sure that each lint only lints one behavior. How lint levels can be defined is a bit up to rustc or generally speaking, the driver to decide. RFC 3389 might also be relevant here.

  • Are there any performance implications in having several lints in a single crate or in multiple ones (meaning in different lint pass implementations)?

This depends on the chosen architecture and lint structure. This should be documented.

Someone wrote a related block regarding comments in JavaParser: tomassetti.me/java-comments-parsing . The basic problem was deciding where they belong to.

I think it is just not important where the comments are attached to. I know from rust-analyzer experience, that RA has a lossless syntax tree and comments are easily accessible via the AST in its internal code.

I have to think about it (And probably create an issue for further discussion) Thank you for the reference of rust-analyzers implementation

Maybe it would be sufficient to provide a simple representation for semantic generics, without the entire lifetime analysis.

I am not sure what exact differencies you imply between syntactic and semantic lifetimes. Users will definitely not need any results of borrow-checking analysis. I think the following set of operations should cover the use cases.

Every lifetime, actually written by the user, is represented in the syntactic representation of the node. Requesting the syntactic type of &'a u32 includes the 'a lifetime, since the user actually wrote it out. However, the semantic type (the one returned by type checking) is only &u32. The semantic lifetime, is bound to the borrow-checking analysis and including it in the semantic type would be complicated and also require every driver to support borrow-checking. Rustc is currently the only driver, I know of, that can actually do that.

Linting the lifetimes usages on its own is definitely a very narrow and small area, so it's not worth spending too much time here. My comment was basically about the expectation to see at least some lifetime info in the generics of the Cow<'_, str> type, but I found that my lint sees it as just Cow<str> not knowing that this is a borrowed type at all. Having some minimum lifetime context described in the bullet list above may fix the intuitiveness of answering the questions for the lint implementor like "I know Cow has a lifetime parameter. Do I need to skip the first generic arg of that type if I want to ignore it?"

I just tested it, and it's a bug, that the lifetime, is not included in the syntactic type (#208). It works for explicitly named ones, though:

`_cow: Cow<'a, str>`
generics: SynGenericArgs {
    args: [
        Lifetime(
            SynLifetimeArg {
                lifetime: Lifetime {
                    _lifetime: PhantomData<&()>,
                    span: Some(
                        SpanId(..),
                    ),
                    kind: Label(
                        SymbolId(..),
                        GenericId(..),
                    ),
                },
            },
        ),
        Ty(
            SynTyArg {
                ty: Text(
                    SynTextTy {
                        data: CommonSynTyData {
                            _lifetime: PhantomData<&()>,
                            span: SpanId(..),
                        },
                        textual_kind: Str,
                    },
                ),
            },
        ),
    ],
},

Followup questions

What will happen if the lint does something stupid in the LintPass impl?

Lint crates are like proc macros, as they can basically do whatever they want. At the start, I tried to have lint crates sandboxed using web assembly, but this sadly doesn't work, as pointers don't work across the WASM boarder (At least from my testing).

  • Panics

Possible and currently unsound: #10

  • Calls std::process::exit()
  • Calls std::process::abort()

IDK, will probably terminate the process

  • Blocks the thread with thread::sleep or expensive computations or event enters an infinite loop

It will block Marker, having a timeout might be a good feature, for the future:

  • Stores things in global static mutable variables guarded by some synchronization primitive
  • Stores things in global thread_local mutable variables. Basically, what is the thread interaction environment here?

Totally fine, the documentation (somewhere) defines that a lint crate is always called with the same thread.

  • Uses logging macros from tracing/log crates or even bare [e]print[ln]!()?

Works as normal, this is kind of intentional, as I want to allow marker to be used for other things besides linting. A library could also use a lint crate to generate code, based on the loaded AST structure. Ideally, I would like crates to be more sandboxed with the user specifying permissions, but that is currently sadly not possible. (As far as I can tell)

  • Spawns a new thread or even spins an async runtime

Possible! Same problem as proc macros.

@Veetaha
Copy link
Contributor Author

Veetaha commented Aug 1, 2023

I see, thank you for addressing my comments. I am closing this issue since I consider the questions resolved, and there were created separate issues for this feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-feedback Category: Feedback
Projects
None yet
Development

No branches or pull requests

3 participants