Update MSRV to 1.85 and toolchain to 1.87 (#18126)

This commit is contained in:
Micha Reiser 2025-05-16 09:19:55 +02:00 committed by GitHub
parent 6e39250015
commit 196e4befba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 82 additions and 54 deletions

View File

@ -4,7 +4,7 @@ resolver = "2"
[workspace.package] [workspace.package]
edition = "2021" edition = "2021"
rust-version = "1.84" rust-version = "1.85"
homepage = "https://docs.astral.sh/ruff" homepage = "https://docs.astral.sh/ruff"
documentation = "https://docs.astral.sh/ruff" documentation = "https://docs.astral.sh/ruff"
repository = "https://github.com/astral-sh/ruff" repository = "https://github.com/astral-sh/ruff"
@ -215,6 +215,7 @@ similar_names = "allow"
single_match_else = "allow" single_match_else = "allow"
too_many_lines = "allow" too_many_lines = "allow"
needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block. needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block.
unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often.
# Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250 # Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250
needless_raw_string_hashes = "allow" needless_raw_string_hashes = "allow"
# Disallowed restriction lints # Disallowed restriction lints

View File

@ -133,7 +133,7 @@ struct SourceTextInner {
#[derive(Eq, PartialEq)] #[derive(Eq, PartialEq)]
enum SourceTextKind { enum SourceTextKind {
Text(String), Text(String),
Notebook(Notebook), Notebook(Box<Notebook>),
} }
impl From<String> for SourceTextKind { impl From<String> for SourceTextKind {
@ -144,7 +144,7 @@ impl From<String> for SourceTextKind {
impl From<Notebook> for SourceTextKind { impl From<Notebook> for SourceTextKind {
fn from(notebook: Notebook) -> Self { fn from(notebook: Notebook) -> Self {
SourceTextKind::Notebook(notebook) SourceTextKind::Notebook(Box::new(notebook))
} }
} }

View File

@ -463,17 +463,17 @@ fn not_found() -> std::io::Error {
fn is_a_directory() -> std::io::Error { fn is_a_directory() -> std::io::Error {
// Note: Rust returns `ErrorKind::IsADirectory` for this error but this is a nightly only variant :(. // Note: Rust returns `ErrorKind::IsADirectory` for this error but this is a nightly only variant :(.
// So we have to use other for now. // So we have to use other for now.
std::io::Error::new(std::io::ErrorKind::Other, "Is a directory") std::io::Error::other("Is a directory")
} }
fn not_a_directory() -> std::io::Error { fn not_a_directory() -> std::io::Error {
// Note: Rust returns `ErrorKind::NotADirectory` for this error but this is a nightly only variant :(. // Note: Rust returns `ErrorKind::NotADirectory` for this error but this is a nightly only variant :(.
// So we have to use `Other` for now. // So we have to use `Other` for now.
std::io::Error::new(std::io::ErrorKind::Other, "Not a directory") std::io::Error::other("Not a directory")
} }
fn directory_not_empty() -> std::io::Error { fn directory_not_empty() -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::Other, "directory not empty") std::io::Error::other("directory not empty")
} }
fn create_dir_all( fn create_dir_all(

View File

@ -1388,7 +1388,7 @@ pub fn soft_space_or_block_indent<Context>(content: &impl Format<Context>) -> Bl
pub fn group<Context>(content: &impl Format<Context>) -> Group<Context> { pub fn group<Context>(content: &impl Format<Context>) -> Group<Context> {
Group { Group {
content: Argument::new(content), content: Argument::new(content),
group_id: None, id: None,
should_expand: false, should_expand: false,
} }
} }
@ -1396,14 +1396,14 @@ pub fn group<Context>(content: &impl Format<Context>) -> Group<Context> {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct Group<'a, Context> { pub struct Group<'a, Context> {
content: Argument<'a, Context>, content: Argument<'a, Context>,
group_id: Option<GroupId>, id: Option<GroupId>,
should_expand: bool, should_expand: bool,
} }
impl<Context> Group<'_, Context> { impl<Context> Group<'_, Context> {
#[must_use] #[must_use]
pub fn with_group_id(mut self, group_id: Option<GroupId>) -> Self { pub fn with_id(mut self, group_id: Option<GroupId>) -> Self {
self.group_id = group_id; self.id = group_id;
self self
} }
@ -1429,7 +1429,7 @@ impl<Context> Format<Context> for Group<'_, Context> {
}; };
f.write_element(FormatElement::Tag(StartGroup( f.write_element(FormatElement::Tag(StartGroup(
tag::Group::new().with_id(self.group_id).with_mode(mode), tag::Group::new().with_id(self.id).with_mode(mode),
))); )));
Arguments::from(&self.content).fmt(f)?; Arguments::from(&self.content).fmt(f)?;
@ -1443,7 +1443,7 @@ impl<Context> Format<Context> for Group<'_, Context> {
impl<Context> std::fmt::Debug for Group<'_, Context> { impl<Context> std::fmt::Debug for Group<'_, Context> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Group") f.debug_struct("Group")
.field("group_id", &self.group_id) .field("id", &self.id)
.field("should_expand", &self.should_expand) .field("should_expand", &self.should_expand)
.field("content", &"{{content}}") .field("content", &"{{content}}")
.finish() .finish()
@ -1642,7 +1642,7 @@ impl<Context> std::fmt::Debug for BestFitParenthesize<'_, Context> {
/// soft_line_break(), /// soft_line_break(),
/// if_group_breaks(&token(")")) /// if_group_breaks(&token(")"))
/// ]) /// ])
/// .with_group_id(Some(parentheses_id)) /// .with_id(Some(parentheses_id))
/// .fmt(f) /// .fmt(f)
/// }); /// });
/// ///
@ -1991,7 +1991,7 @@ impl<Context> IfGroupBreaks<'_, Context> {
/// })), /// })),
/// token("]") /// token("]")
/// ], /// ],
/// ).with_group_id(Some(group_id)) /// ).with_id(Some(group_id))
/// ]) /// ])
/// })])?; /// })])?;
/// ///
@ -2046,7 +2046,7 @@ impl<Context> std::fmt::Debug for IfGroupBreaks<'_, Context> {
/// let id = f.group_id("head"); /// let id = f.group_id("head");
/// ///
/// write!(f, [ /// write!(f, [
/// group(&token("Head")).with_group_id(Some(id)), /// group(&token("Head")).with_id(Some(id)),
/// if_group_breaks(&indent(&token("indented"))).with_group_id(Some(id)), /// if_group_breaks(&indent(&token("indented"))).with_group_id(Some(id)),
/// if_group_fits_on_line(&token("indented")).with_group_id(Some(id)) /// if_group_fits_on_line(&token("indented")).with_group_id(Some(id))
/// ]) /// ])
@ -2071,7 +2071,7 @@ impl<Context> std::fmt::Debug for IfGroupBreaks<'_, Context> {
/// let group_id = f.group_id("header"); /// let group_id = f.group_id("header");
/// ///
/// write!(f, [ /// write!(f, [
/// group(&token("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)), /// group(&token("(aLongHeaderThatBreaksForSomeReason) =>")).with_id(Some(group_id)),
/// indent_if_group_breaks(&format_args![hard_line_break(), token("a => b")], group_id) /// indent_if_group_breaks(&format_args![hard_line_break(), token("a => b")], group_id)
/// ]) /// ])
/// }); /// });
@ -2101,7 +2101,7 @@ impl<Context> std::fmt::Debug for IfGroupBreaks<'_, Context> {
/// let group_id = f.group_id("header"); /// let group_id = f.group_id("header");
/// ///
/// write!(f, [ /// write!(f, [
/// group(&token("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)), /// group(&token("(aLongHeaderThatBreaksForSomeReason) =>")).with_id(Some(group_id)),
/// indent_if_group_breaks(&format_args![hard_line_break(), token("a => b")], group_id) /// indent_if_group_breaks(&format_args![hard_line_break(), token("a => b")], group_id)
/// ]) /// ])
/// }); /// });

View File

@ -2002,7 +2002,7 @@ two lines`,
token("The referenced group breaks."), token("The referenced group breaks."),
hard_line_break() hard_line_break()
]) ])
.with_group_id(Some(group_id)), .with_id(Some(group_id)),
group(&format_args![ group(&format_args![
token("This group breaks because:"), token("This group breaks because:"),
soft_line_break_or_space(), soft_line_break_or_space(),
@ -2027,7 +2027,7 @@ two lines`,
write!( write!(
f, f,
[ [
group(&token("Group with id-2")).with_group_id(Some(id_2)), group(&token("Group with id-2")).with_id(Some(id_2)),
hard_line_break() hard_line_break()
] ]
)?; )?;
@ -2035,7 +2035,7 @@ two lines`,
write!( write!(
f, f,
[ [
group(&token("Group with id-1 does not fit on the line because it exceeds the line width of 80 characters by")).with_group_id(Some(id_1)), group(&token("Group with id-1 does not fit on the line because it exceeds the line width of 80 characters by")).with_id(Some(id_1)),
hard_line_break() hard_line_break()
] ]
)?; )?;

View File

@ -940,7 +940,7 @@ mod tests {
#[test_case(Path::new("add_missing_cell_id.ipynb"), true; "add_missing_cell_id")] #[test_case(Path::new("add_missing_cell_id.ipynb"), true; "add_missing_cell_id")]
fn test_cell_id(path: &Path, has_id: bool) -> Result<()> { fn test_cell_id(path: &Path, has_id: bool) -> Result<()> {
let source_notebook = Notebook::from_path(&notebook_path(path))?; let source_notebook = Notebook::from_path(&notebook_path(path))?;
let source_kind = SourceKind::IpyNotebook(source_notebook); let source_kind = SourceKind::ipy_notebook(source_notebook);
let (_, transformed) = test_contents( let (_, transformed) = test_contents(
&source_kind, &source_kind,
path, path,
@ -1231,7 +1231,7 @@ mod tests {
format!("async_comprehension_in_sync_comprehension_notebook_{python_version}"); format!("async_comprehension_in_sync_comprehension_notebook_{python_version}");
let path = Path::new("resources/test/fixtures/syntax_errors/async_comprehension.ipynb"); let path = Path::new("resources/test/fixtures/syntax_errors/async_comprehension.ipynb");
let messages = test_contents_syntax_errors( let messages = test_contents_syntax_errors(
&SourceKind::IpyNotebook(Notebook::from_path(path)?), &SourceKind::ipy_notebook(Notebook::from_path(path)?),
path, path,
&LinterSettings { &LinterSettings {
unresolved_target_version: python_version.into(), unresolved_target_version: python_version.into(),

View File

@ -198,7 +198,7 @@ fn is_explicit_concatenation(expr: &Expr) -> Option<bool> {
.iter() .iter()
.map(is_explicit_concatenation) .map(is_explicit_concatenation)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if values.iter().any(|v| *v == Some(true)) { if values.contains(&Some(true)) {
Some(true) Some(true)
} else if values.iter().all(|v| *v == Some(false)) { } else if values.iter().all(|v| *v == Some(false)) {
Some(false) Some(false)

View File

@ -174,7 +174,7 @@ struct StringLiteralDisplay<'a> {
/// The elts from the original AST node representing the display. /// The elts from the original AST node representing the display.
/// Each elt is the AST representation of a single string literal /// Each elt is the AST representation of a single string literal
/// element in the display /// element in the display
elts: Cow<'a, Vec<ast::Expr>>, elts: Cow<'a, [ast::Expr]>,
/// The source-code range of the display as a whole /// The source-code range of the display as a whole
range: TextRange, range: TextRange,
/// What kind of a display is it? A dict, set, list or tuple? /// What kind of a display is it? A dict, set, list or tuple?

View File

@ -16,15 +16,47 @@ use colored::Colorize;
use crate::fs; use crate::fs;
use crate::text_helpers::ShowNonprinting; use crate::text_helpers::ShowNonprinting;
#[derive(Clone, Debug, PartialEq, is_macro::Is)] #[derive(Clone, Debug, PartialEq)]
pub enum SourceKind { pub enum SourceKind {
/// The source contains Python source code. /// The source contains Python source code.
Python(String), Python(String),
/// The source contains a Jupyter notebook. /// The source contains a Jupyter notebook.
IpyNotebook(Notebook), IpyNotebook(Box<Notebook>),
} }
impl SourceKind { impl SourceKind {
pub fn ipy_notebook(notebook: Notebook) -> Self {
SourceKind::IpyNotebook(Box::new(notebook))
}
pub fn as_ipy_notebook(&self) -> Option<&Notebook> {
match self {
SourceKind::IpyNotebook(notebook) => Some(notebook),
SourceKind::Python(_) => None,
}
}
pub fn as_python(&self) -> Option<&str> {
match self {
SourceKind::Python(code) => Some(code),
SourceKind::IpyNotebook(_) => None,
}
}
pub fn expect_python(self) -> String {
match self {
SourceKind::Python(code) => code,
SourceKind::IpyNotebook(_) => panic!("expected python code"),
}
}
pub fn expect_ipy_notebook(self) -> Notebook {
match self {
SourceKind::IpyNotebook(notebook) => *notebook,
SourceKind::Python(_) => panic!("expected ipy notebook"),
}
}
#[must_use] #[must_use]
pub(crate) fn updated(&self, new_source: String, source_map: &SourceMap) -> Self { pub(crate) fn updated(&self, new_source: String, source_map: &SourceMap) -> Self {
match self { match self {
@ -52,7 +84,7 @@ impl SourceKind {
let notebook = Notebook::from_path(path)?; let notebook = Notebook::from_path(path)?;
Ok(notebook Ok(notebook
.is_python_notebook() .is_python_notebook()
.then_some(Self::IpyNotebook(notebook))) .then_some(Self::IpyNotebook(Box::new(notebook))))
} else { } else {
let contents = std::fs::read_to_string(path)?; let contents = std::fs::read_to_string(path)?;
Ok(Some(Self::Python(contents))) Ok(Some(Self::Python(contents)))
@ -69,7 +101,7 @@ impl SourceKind {
let notebook = Notebook::from_source_code(&source_code)?; let notebook = Notebook::from_source_code(&source_code)?;
Ok(notebook Ok(notebook
.is_python_notebook() .is_python_notebook()
.then_some(Self::IpyNotebook(notebook))) .then_some(Self::IpyNotebook(Box::new(notebook))))
} else { } else {
Ok(Some(Self::Python(source_code))) Ok(Some(Self::Python(source_code)))
} }

View File

@ -60,7 +60,7 @@ pub(crate) fn assert_notebook_path(
) -> Result<TestedNotebook, NotebookError> { ) -> Result<TestedNotebook, NotebookError> {
let source_notebook = Notebook::from_path(path.as_ref())?; let source_notebook = Notebook::from_path(path.as_ref())?;
let source_kind = SourceKind::IpyNotebook(source_notebook); let source_kind = SourceKind::ipy_notebook(source_notebook);
let (messages, transformed) = test_contents(&source_kind, path.as_ref(), settings); let (messages, transformed) = test_contents(&source_kind, path.as_ref(), settings);
let expected_notebook = Notebook::from_path(expected.as_ref())?; let expected_notebook = Notebook::from_path(expected.as_ref())?;
let linted_notebook = transformed.into_owned().expect_ipy_notebook(); let linted_notebook = transformed.into_owned().expect_ipy_notebook();

View File

@ -255,7 +255,7 @@ impl<'ast> Format<PyFormatContext<'ast>> for FormatOptionalParentheses<'_, 'ast>
soft_line_break(), soft_line_break(),
if_group_breaks(&token(")")) if_group_breaks(&token(")"))
]) ])
.with_group_id(Some(parens_id))] .with_id(Some(parens_id))]
) )
} }
} }

View File

@ -413,7 +413,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
soft_block_indent(&format_args![flat, inline_comments]), soft_block_indent(&format_args![flat, inline_comments]),
token(")"), token(")"),
]) ])
.with_group_id(Some(group_id)) .with_id(Some(group_id))
.should_expand(true) .should_expand(true)
.fmt(f) .fmt(f)
}); });
@ -433,7 +433,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
token(")"), token(")"),
inline_comments, inline_comments,
]) ])
.with_group_id(Some(group_id)) .with_id(Some(group_id))
.should_expand(true) .should_expand(true)
.fmt(f) .fmt(f)
}); });
@ -501,7 +501,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
soft_block_indent(&format_args![f_string_flat, inline_comments]), soft_block_indent(&format_args![f_string_flat, inline_comments]),
token(")"), token(")"),
]) ])
.with_group_id(Some(group_id)) .with_id(Some(group_id))
.should_expand(true) .should_expand(true)
.fmt(f) .fmt(f)
}); });
@ -817,7 +817,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
space(), space(),
token("("), token("("),
group(&soft_block_indent(&format_expanded)) group(&soft_block_indent(&format_expanded))
.with_group_id(Some(group_id)) .with_id(Some(group_id))
.should_expand(true), .should_expand(true),
token(")"), token(")"),
inline_comments inline_comments
@ -875,7 +875,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
space(), space(),
token("("), token("("),
group(&soft_block_indent(&format_expanded)) group(&soft_block_indent(&format_expanded))
.with_group_id(Some(group_id)) .with_id(Some(group_id))
.should_expand(true), .should_expand(true),
token(")"), token(")"),
inline_comments inline_comments

View File

@ -252,9 +252,7 @@ pub fn is_immutable_annotation(
.is_some_and(|qualified_name| { .is_some_and(|qualified_name| {
is_immutable_non_generic_type(qualified_name.segments()) is_immutable_non_generic_type(qualified_name.segments())
|| is_immutable_generic_type(qualified_name.segments()) || is_immutable_generic_type(qualified_name.segments())
|| extend_immutable_calls || extend_immutable_calls.contains(&qualified_name)
.iter()
.any(|target| qualified_name == *target)
}) })
} }
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => semantic Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => semantic
@ -308,9 +306,7 @@ pub fn is_immutable_func(
.resolve_qualified_name(map_subscript(func)) .resolve_qualified_name(map_subscript(func))
.is_some_and(|qualified_name| { .is_some_and(|qualified_name| {
is_immutable_return_type(qualified_name.segments()) is_immutable_return_type(qualified_name.segments())
|| extend_immutable_calls || extend_immutable_calls.contains(&qualified_name)
.iter()
.any(|target| qualified_name == *target)
}) })
} }

View File

@ -559,7 +559,7 @@ impl DocumentQuery {
ruff_linter::source_kind::SourceKind::Python(document.contents().to_string()) ruff_linter::source_kind::SourceKind::Python(document.contents().to_string())
} }
Self::Notebook { notebook, .. } => { Self::Notebook { notebook, .. } => {
ruff_linter::source_kind::SourceKind::IpyNotebook(notebook.make_ruff_notebook()) ruff_linter::source_kind::SourceKind::ipy_notebook(notebook.make_ruff_notebook())
} }
} }
} }

View File

@ -462,7 +462,7 @@ impl ConfigurationTransformer for EditorConfigurationTransformer<'_> {
tracing::debug!( tracing::debug!(
"Combining settings from editor-specified inline configuration" "Combining settings from editor-specified inline configuration"
); );
match Configuration::from_options(options, None, project_root) { match Configuration::from_options(*options, None, project_root) {
Ok(configuration) => editor_configuration.combine(configuration), Ok(configuration) => editor_configuration.combine(configuration),
Err(err) => { Err(err) => {
tracing::error!( tracing::error!(
@ -516,10 +516,10 @@ mod tests {
#[test] #[test]
fn inline_settings() { fn inline_settings() {
let editor_settings = ResolvedEditorSettings { let editor_settings = ResolvedEditorSettings {
configuration: Some(ResolvedConfiguration::Inline(Options { configuration: Some(ResolvedConfiguration::Inline(Box::new(Options {
line_length: Some(LineLength::try_from(120).unwrap()), line_length: Some(LineLength::try_from(120).unwrap()),
..Default::default() ..Default::default()
})), }))),
..Default::default() ..Default::default()
}; };
@ -534,10 +534,10 @@ mod tests {
#[test] #[test]
fn inline_and_specific_settings_resolution_order() { fn inline_and_specific_settings_resolution_order() {
let editor_settings = ResolvedEditorSettings { let editor_settings = ResolvedEditorSettings {
configuration: Some(ResolvedConfiguration::Inline(Options { configuration: Some(ResolvedConfiguration::Inline(Box::new(Options {
line_length: Some(LineLength::try_from(120).unwrap()), line_length: Some(LineLength::try_from(120).unwrap()),
..Default::default() ..Default::default()
})), }))),
line_length: Some(LineLength::try_from(100).unwrap()), line_length: Some(LineLength::try_from(100).unwrap()),
..Default::default() ..Default::default()
}; };

View File

@ -52,7 +52,7 @@ pub(crate) struct ResolvedEditorSettings {
#[cfg_attr(test, derive(PartialEq, Eq))] #[cfg_attr(test, derive(PartialEq, Eq))]
pub(crate) enum ResolvedConfiguration { pub(crate) enum ResolvedConfiguration {
FilePath(PathBuf), FilePath(PathBuf),
Inline(Options), Inline(Box<Options>),
} }
impl TryFrom<&ClientConfiguration> for ResolvedConfiguration { impl TryFrom<&ClientConfiguration> for ResolvedConfiguration {
@ -68,7 +68,7 @@ impl TryFrom<&ClientConfiguration> for ResolvedConfiguration {
if options.extend.is_some() { if options.extend.is_some() {
Err(ResolvedConfigurationError::ExtendNotSupported) Err(ResolvedConfigurationError::ExtendNotSupported)
} else { } else {
Ok(ResolvedConfiguration::Inline(options)) Ok(ResolvedConfiguration::Inline(Box::new(options)))
} }
} }
} }
@ -991,7 +991,7 @@ mod tests {
fix_violation_enable: true, fix_violation_enable: true,
show_syntax_errors: true, show_syntax_errors: true,
editor_settings: ResolvedEditorSettings { editor_settings: ResolvedEditorSettings {
configuration: Some(ResolvedConfiguration::Inline(Options { configuration: Some(ResolvedConfiguration::Inline(Box::new(Options {
line_length: Some(LineLength::try_from(100).unwrap()), line_length: Some(LineLength::try_from(100).unwrap()),
lint: Some(LintOptions { lint: Some(LintOptions {
common: LintCommonOptions { common: LintCommonOptions {
@ -1005,7 +1005,7 @@ mod tests {
..Default::default() ..Default::default()
}), }),
..Default::default() ..Default::default()
})), }))),
extend_select: Some(vec![RuleSelector::from_str("RUF001").unwrap()]), extend_select: Some(vec![RuleSelector::from_str("RUF001").unwrap()]),
..Default::default() ..Default::default()
} }

View File

@ -594,7 +594,7 @@ impl PythonHomePath {
system system
.is_directory(&canonicalized) .is_directory(&canonicalized)
.then_some(Self(canonicalized)) .then_some(Self(canonicalized))
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "not a directory")) .ok_or_else(|| io::Error::other("not a directory"))
} }
} }

View File

@ -5034,7 +5034,6 @@ impl<'db> Type<'db> {
/// Note that this does not specialize generic classes, functions, or type aliases! That is a /// Note that this does not specialize generic classes, functions, or type aliases! That is a
/// different operation that is performed explicitly (via a subscript operation), or implicitly /// different operation that is performed explicitly (via a subscript operation), or implicitly
/// via a call to the generic object. /// via a call to the generic object.
#[must_use]
#[salsa::tracked] #[salsa::tracked]
pub fn apply_specialization( pub fn apply_specialization(
self, self,

View File

@ -1,2 +1,2 @@
[toolchain] [toolchain]
channel = "1.86" channel = "1.87"