Add support for configuring knot in pyproject.toml files (#15493)

## Summary

This PR adds support for configuring Red Knot in the `tool.knot` section
of the project's
`pyproject.toml` section. Options specified on the CLI precede the
options in the configuration file.

This PR only supports the `environment` and the `src.root` options for
now.
Other options will be added as separate PRs.

There are also a few concerns that I intentionally ignored as part of
this PR:

* Handling of relative paths: We need to anchor paths relative to the
current working directory (CLI), or the project (`pyproject.toml` or
`knot.toml`)
* Tracking the source of a value. Diagnostics would benefit from knowing
from which configuration a value comes so that we can point the user to
the right configuration file (or CLI) if the configuration is invalid.
* Schema generation and there's a lot more; see
https://github.com/astral-sh/ruff/issues/15491

This PR changes the default for first party codes: Our existing default
was to only add the project root. Now, Red Knot adds the project root
and `src` (if such a directory exists).

Theoretically, we'd have to add a file watcher event that changes the
first-party search paths if a user later creates a `src` directory. I
think this is pretty uncommon, which is why I ignored the complexity for
now but I can be persuaded to handle it if it's considered important.

Part of https://github.com/astral-sh/ruff/issues/15491

## Test Plan

Existing tests, new file watching test demonstrating that changing the
python version and platform is correctly reflected.
This commit is contained in:
Micha Reiser 2025-01-17 09:41:06 +01:00 committed by GitHub
parent 9ed67ba33e
commit eb47a6634d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 820 additions and 413 deletions

View file

@ -0,0 +1,43 @@
use quote::{quote, quote_spanned};
use syn::{Data, DataStruct, DeriveInput, Fields};
pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let DeriveInput { ident, data, .. } = input;
match data {
Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) => {
let output: Vec<_> = fields
.named
.iter()
.map(|field| {
let ident = field
.ident
.as_ref()
.expect("Expected to handle named fields");
quote_spanned!(
ident.span() => crate::project::combine::Combine::combine_with(&mut self.#ident, other.#ident)
)
})
.collect();
Ok(quote! {
#[automatically_derived]
impl crate::project::combine::Combine for #ident {
fn combine_with(&mut self, other: Self) {
#(
#output
);*
}
}
})
}
_ => Err(syn::Error::new(
ident.span(),
"Can only derive Combine from structs with named fields.",
)),
}
}

View file

@ -7,6 +7,7 @@ use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput, Error, ItemFn, ItemStruct};
mod cache_key;
mod combine;
mod combine_options;
mod config;
mod derive_message_formats;
@ -35,6 +36,19 @@ pub fn derive_combine_options(input: TokenStream) -> TokenStream {
.into()
}
/// Automatically derives a `red_knot_workspace::project::Combine` implementation for the attributed type
/// that calls `red_knot_workspace::project::Combine::combine` for each field.
///
/// The derive macro can only be used on structs with named fields.
#[proc_macro_derive(Combine)]
pub fn derive_combine(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
combine::derive_impl(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
/// Converts a screaming snake case identifier to a kebab case string.
#[proc_macro]
pub fn kebab_case(input: TokenStream) -> TokenStream {