Compare commits
2 Commits
alex/union
...
micha/bye-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
134d32ae05 | ||
|
|
f43119551c |
2
.github/workflows/mypy_primer.yaml
vendored
2
.github/workflows/mypy_primer.yaml
vendored
@@ -60,7 +60,6 @@ jobs:
|
||||
|
||||
- name: Run mypy_primer
|
||||
env:
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
|
||||
CLICOLOR_FORCE: "1"
|
||||
DIFF_FILE: mypy_primer.diff
|
||||
run: |
|
||||
@@ -142,7 +141,6 @@ jobs:
|
||||
- name: Run determinism check
|
||||
env:
|
||||
BASE_REVISION: ${{ github.event.pull_request.head.sha }}
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
|
||||
CLICOLOR_FORCE: "1"
|
||||
DIFF_FILE: mypy_primer_determinism.diff
|
||||
run: |
|
||||
|
||||
4
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
4
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -62,13 +62,11 @@ jobs:
|
||||
echo "new commit"
|
||||
git checkout -b new_commit "$GITHUB_SHA"
|
||||
git rev-list --format=%s --max-count=1 new_commit
|
||||
cp crates/ty_python_semantic/resources/primer/good.txt projects_new.txt
|
||||
|
||||
echo "old commit (merge base)"
|
||||
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
|
||||
git checkout -b old_commit "$MERGE_BASE"
|
||||
git rev-list --format=%s --max-count=1 old_commit
|
||||
cp crates/ty_python_semantic/resources/primer/good.txt projects_old.txt
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -78,8 +76,6 @@ jobs:
|
||||
--repository ruff \
|
||||
diff \
|
||||
--profile=profiling \
|
||||
--projects-old ruff/projects_old.txt \
|
||||
--projects-new ruff/projects_new.txt \
|
||||
--old old_commit \
|
||||
--new new_commit \
|
||||
--output-old diagnostics-old.json \
|
||||
|
||||
1
.github/workflows/ty-ecosystem-report.yaml
vendored
1
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -62,7 +62,6 @@ jobs:
|
||||
--repository ruff \
|
||||
analyze \
|
||||
--profile=profiling \
|
||||
--projects ruff/crates/ty_python_semantic/resources/primer/good.txt \
|
||||
--output ecosystem-diagnostics.json
|
||||
|
||||
mkdir dist
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
class Simple:
|
||||
x=1
|
||||
x=2 # fmt: skip
|
||||
x=3
|
||||
|
||||
class Semicolon:
|
||||
x=1
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class TrailingSemicolon:
|
||||
x=1
|
||||
x=2;x=3 ; # fmt: skip
|
||||
x=4
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
x=1;
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class CompoundInSuite:
|
||||
x=1
|
||||
def foo(): y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x=1
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class MultiLineSkip:
|
||||
x=1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
] # fmt: skip
|
||||
|
||||
class MultiLineSemicolon:
|
||||
x=1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
]; x=2 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAfter:
|
||||
x=1
|
||||
x = ['a']\
|
||||
; y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonBefore:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAndNewline:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
|
||||
y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAndNewlineAndComment:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
# 1
|
||||
y=1 # fmt: skip
|
||||
|
||||
class RepeatedLineContinuation:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
\
|
||||
\
|
||||
y=1 # fmt: skip
|
||||
|
||||
class MultiLineSemicolonComments:
|
||||
x=1
|
||||
# 1
|
||||
x = [ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
# 4
|
||||
]; x=2 # 5 # fmt: skip # 6
|
||||
|
||||
class DocstringSkipped:
|
||||
'''This is a docstring''' # fmt: skip
|
||||
x=1
|
||||
|
||||
class MultilineDocstringSkipped:
|
||||
'''This is a docstring
|
||||
''' # fmt: skip
|
||||
x=1
|
||||
|
||||
class FirstStatementNewlines:
|
||||
|
||||
|
||||
|
||||
|
||||
x=1 # fmt: skip
|
||||
|
||||
class ChainingSemicolons:
|
||||
x=[
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
];x=1;x=[
|
||||
'1',
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # fmt: skip
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
class MixingCompound:
|
||||
def foo(): bar(); import zoo # fmt: skip
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/17331
|
||||
def main() -> None:
|
||||
import ipdb; ipdb.set_trace() # noqa: E402,E702,I001 # fmt: skip
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/11430
|
||||
print(); print() # noqa # fmt: skip
|
||||
@@ -1,22 +0,0 @@
|
||||
# 0
|
||||
'''Module docstring
|
||||
multiple lines''' # 1 # fmt: skip # 2
|
||||
|
||||
import a;import b; from c import (
|
||||
x,y,z
|
||||
); import f # fmt: skip
|
||||
|
||||
x=1;x=2;x=3;x=4 # fmt: skip
|
||||
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
def foo(): x=[
|
||||
'1',
|
||||
'2',
|
||||
];x=1 # fmt: skip
|
||||
@@ -1,68 +0,0 @@
|
||||
class Simple:
|
||||
# Range comprises skip range
|
||||
x=1
|
||||
<RANGE_START>x=2 <RANGE_END># fmt: skip
|
||||
x=3
|
||||
|
||||
class Semicolon:
|
||||
# Range is part of skip range
|
||||
x=1
|
||||
x=2;<RANGE_START>x=3<RANGE_END> # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatFirst:
|
||||
x=1
|
||||
<RANGE_START>x=2<RANGE_END>;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatMiddle:
|
||||
x=1
|
||||
x=2;<RANGE_START>x=3<RANGE_END>;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on right side
|
||||
<RANGE_START>x=1;
|
||||
x=2<RANGE_END>;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on left side
|
||||
x=1;
|
||||
x=2;<RANGE_START>x=3 # fmt: skip
|
||||
x=4<RANGE_END>
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class CompoundInSuite:
|
||||
x=1
|
||||
<RANGE_START>def foo(): y=1 <RANGE_END># fmt: skip
|
||||
x=2
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x=1
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class MultiLineSkip:
|
||||
# Range inside statement
|
||||
x=1
|
||||
x = <RANGE_START>[
|
||||
'1',
|
||||
'2',<RANGE_END>
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3<RANGE_START>
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4<RANGE_END> # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
@@ -24,6 +24,7 @@ pub use crate::options::{
|
||||
};
|
||||
use crate::range::is_logical_line;
|
||||
pub use crate::shared_traits::{AsFormat, FormattedIter, FormattedIterExt, IntoFormat};
|
||||
use crate::verbatim::suppressed_node;
|
||||
|
||||
pub(crate) mod builders;
|
||||
pub mod cli;
|
||||
@@ -60,39 +61,51 @@ where
|
||||
let node_ref = AnyNodeRef::from(node);
|
||||
let node_comments = comments.leading_dangling_trailing(node_ref);
|
||||
|
||||
leading_comments(node_comments.leading).fmt(f)?;
|
||||
if self.is_suppressed(node_comments.trailing, f.context()) {
|
||||
suppressed_node(node_ref).fmt(f)
|
||||
} else {
|
||||
leading_comments(node_comments.leading).fmt(f)?;
|
||||
|
||||
// Emit source map information for nodes that are valid "narrowing" targets
|
||||
// in range formatting. Never emit source map information if they're disabled
|
||||
// for performance reasons.
|
||||
let emit_source_position = (is_logical_line(node_ref) || node_ref.is_mod_module())
|
||||
&& f.options().source_map_generation().is_enabled();
|
||||
// Emit source map information for nodes that are valid "narrowing" targets
|
||||
// in range formatting. Never emit source map information if they're disabled
|
||||
// for performance reasons.
|
||||
let emit_source_position = (is_logical_line(node_ref) || node_ref.is_mod_module())
|
||||
&& f.options().source_map_generation().is_enabled();
|
||||
|
||||
emit_source_position
|
||||
.then_some(source_position(node.start()))
|
||||
.fmt(f)?;
|
||||
emit_source_position
|
||||
.then_some(source_position(node.start()))
|
||||
.fmt(f)?;
|
||||
|
||||
self.fmt_fields(node, f)?;
|
||||
self.fmt_fields(node, f)?;
|
||||
|
||||
debug_assert!(
|
||||
node_comments
|
||||
.dangling
|
||||
.iter()
|
||||
.all(SourceComment::is_formatted),
|
||||
"The node has dangling comments that need to be formatted manually. Add the special dangling comments handling to `fmt_fields`."
|
||||
);
|
||||
debug_assert!(
|
||||
node_comments
|
||||
.dangling
|
||||
.iter()
|
||||
.all(SourceComment::is_formatted),
|
||||
"The node has dangling comments that need to be formatted manually. Add the special dangling comments handling to `fmt_fields`."
|
||||
);
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
emit_source_position.then_some(source_position(node.end())),
|
||||
trailing_comments(node_comments.trailing)
|
||||
]
|
||||
)
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
emit_source_position.then_some(source_position(node.end())),
|
||||
trailing_comments(node_comments.trailing)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats the node's fields.
|
||||
fn fmt_fields(&self, item: &N, f: &mut PyFormatter) -> FormatResult<()>;
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
_trailing_comments: &[SourceComment],
|
||||
_context: &PyFormatContext,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, salsa::Update, PartialEq, Eq)]
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::Decorator;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::verbatim::verbatim_text;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -12,27 +11,26 @@ pub struct FormatDecorator;
|
||||
|
||||
impl FormatNodeRule<Decorator> for FormatDecorator {
|
||||
fn fmt_fields(&self, item: &Decorator, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let comments = f.context().comments();
|
||||
let trailing = comments.trailing(item);
|
||||
let Decorator {
|
||||
expression,
|
||||
range: _,
|
||||
node_index: _,
|
||||
} = item;
|
||||
|
||||
if has_skip_comment(trailing, f.context().source()) {
|
||||
comments.mark_verbatim_node_comments_formatted(item.into());
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
token("@"),
|
||||
maybe_parenthesize_expression(expression, item, Parenthesize::Optional)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
verbatim_text(item.range()).fmt(f)
|
||||
} else {
|
||||
let Decorator {
|
||||
expression,
|
||||
range: _,
|
||||
node_index: _,
|
||||
} = item;
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
token("@"),
|
||||
maybe_parenthesize_expression(expression, item, Parenthesize::Optional)
|
||||
]
|
||||
)
|
||||
}
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use crate::comments::Comments;
|
||||
use crate::context::{IndentLevel, NodeLevel};
|
||||
use crate::prelude::*;
|
||||
use crate::statement::suite::{DocstringStmt, skip_range};
|
||||
use crate::statement::suite::DocstringStmt;
|
||||
use crate::verbatim::{ends_suppression, starts_suppression};
|
||||
use crate::{FormatModuleError, PyFormatOptions, format_module_source};
|
||||
|
||||
@@ -251,29 +251,7 @@ impl<'ast> SourceOrderVisitor<'ast> for FindEnclosingNode<'_, 'ast> {
|
||||
// We only visit statements that aren't suppressed that's why we don't need to track the suppression
|
||||
// state in a stack. Assert that this assumption is safe.
|
||||
debug_assert!(self.suppressed.is_no());
|
||||
|
||||
let mut iter = body.iter();
|
||||
|
||||
while let Some(stmt) = iter.next() {
|
||||
// If the range intersects a skip range then we need to
|
||||
// format the entire suite to properly handle the case
|
||||
// where a `fmt: skip` affects multiple statements.
|
||||
//
|
||||
// For example, in the case
|
||||
//
|
||||
// ```
|
||||
// <RANGE_START>x=1<RANGE_END>;x=2 # fmt: skip
|
||||
// ```
|
||||
//
|
||||
// the statement `x=1` does not "know" that it is
|
||||
// suppressed, but the suite does.
|
||||
if let Some(verbatim_range) = skip_range(stmt, iter.as_slice(), self.context)
|
||||
&& verbatim_range.intersect(self.range).is_some()
|
||||
{
|
||||
break;
|
||||
}
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
walk_body(self, body);
|
||||
self.suppressed = Suppressed::No;
|
||||
}
|
||||
}
|
||||
@@ -583,7 +561,7 @@ impl NarrowRange<'_> {
|
||||
}
|
||||
|
||||
pub(crate) const fn is_logical_line(node: AnyNodeRef) -> bool {
|
||||
// Make sure to update [`FormatEnclosingNode`] when changing this.
|
||||
// Make sure to update [`FormatEnclosingLine`] when changing this.
|
||||
node.is_statement()
|
||||
|| node.is_decorator()
|
||||
|| node.is_except_handler()
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtAnnAssign;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::is_splittable_expression;
|
||||
use crate::expression::parentheses::Parentheses;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_assign::{
|
||||
AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression,
|
||||
};
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtAnnAssign;
|
||||
@@ -83,4 +84,12 @@ impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ use ruff_formatter::prelude::{space, token};
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtAssert;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtAssert;
|
||||
@@ -40,4 +41,12 @@ impl FormatNodeRule<StmtAssert> for FormatStmtAssert {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use crate::expression::{
|
||||
maybe_parenthesize_expression,
|
||||
};
|
||||
use crate::other::interpolated_string::InterpolatedStringLayout;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_parenthesize_lambda_bodies_enabled;
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::string::StringLikeExtensions;
|
||||
@@ -27,6 +26,7 @@ use crate::string::implicit::{
|
||||
FormatImplicitConcatenatedStringExpanded, FormatImplicitConcatenatedStringFlat,
|
||||
ImplicitConcatenatedLayout,
|
||||
};
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtAssign;
|
||||
@@ -104,6 +104,14 @@ impl FormatNodeRule<StmtAssign> for FormatStmtAssign {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats a single target with the equal operator.
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtAugAssign;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::parentheses::is_expression_parenthesized;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_assign::{
|
||||
AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression,
|
||||
has_target_own_parentheses,
|
||||
};
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::{AsFormat, FormatNodeRule};
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtAugAssign;
|
||||
@@ -61,4 +62,12 @@ impl FormatNodeRule<StmtAugAssign> for FormatStmtAugAssign {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_python_ast::StmtBreak;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtBreak;
|
||||
@@ -9,4 +10,12 @@ impl FormatNodeRule<StmtBreak> for FormatStmtBreak {
|
||||
fn fmt_fields(&self, _item: &StmtBreak, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
token("break").fmt(f)
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_python_ast::StmtContinue;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtContinue;
|
||||
@@ -9,4 +10,12 @@ impl FormatNodeRule<StmtContinue> for FormatStmtContinue {
|
||||
fn fmt_fields(&self, _item: &StmtContinue, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
token("continue").fmt(f)
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ use ruff_python_ast::StmtDelete;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::builders::{PyFormatterExtensions, parenthesize_if_expands};
|
||||
use crate::comments::dangling_node_comments;
|
||||
use crate::comments::{SourceComment, dangling_node_comments};
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtDelete;
|
||||
@@ -57,4 +57,12 @@ impl FormatNodeRule<StmtDelete> for FormatStmtDelete {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Expr, Operator, StmtExpr};
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtExpr;
|
||||
@@ -28,6 +30,14 @@ impl FormatNodeRule<StmtExpr> for FormatStmtExpr {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
const fn is_arithmetic_like(expression: &Expr) -> bool {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ruff_formatter::{format_args, write};
|
||||
use ruff_python_ast::StmtGlobal;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::has_skip_comment;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -45,4 +47,12 @@ impl FormatNodeRule<StmtGlobal> for FormatStmtGlobal {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use ruff_formatter::{format_args, write};
|
||||
use ruff_python_ast::StmtImport;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtImport;
|
||||
@@ -20,4 +21,12 @@ impl FormatNodeRule<StmtImport> for FormatStmtImport {
|
||||
});
|
||||
write!(f, [token("import"), space(), names])
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ use ruff_python_ast::StmtImportFrom;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::builders::{PyFormatterExtensions, TrailingComma, parenthesize_if_expands};
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::parentheses::parenthesized;
|
||||
use crate::has_skip_comment;
|
||||
use crate::other::identifier::DotDelimitedIdentifier;
|
||||
use crate::prelude::*;
|
||||
|
||||
@@ -70,4 +72,12 @@ impl FormatNodeRule<StmtImportFrom> for FormatStmtImportFrom {
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use ruff_python_ast::StmtIpyEscapeCommand;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtIpyEscapeCommand;
|
||||
@@ -10,4 +11,12 @@ impl FormatNodeRule<StmtIpyEscapeCommand> for FormatStmtIpyEscapeCommand {
|
||||
fn fmt_fields(&self, item: &StmtIpyEscapeCommand, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
source_text_slice(item.range()).fmt(f)
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ruff_formatter::{format_args, write};
|
||||
use ruff_python_ast::StmtNonlocal;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::has_skip_comment;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -45,4 +47,12 @@ impl FormatNodeRule<StmtNonlocal> for FormatStmtNonlocal {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_python_ast::StmtPass;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtPass;
|
||||
@@ -9,4 +10,12 @@ impl FormatNodeRule<StmtPass> for FormatStmtPass {
|
||||
fn fmt_fields(&self, _item: &StmtPass, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
token("pass").fmt(f)
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtRaise;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtRaise;
|
||||
@@ -42,4 +43,12 @@ impl FormatNodeRule<StmtRaise> for FormatStmtRaise {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::{Expr, StmtReturn};
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::expr_tuple::TupleParentheses;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_assign::FormatStatementsLastExpression;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtReturn;
|
||||
@@ -42,4 +43,12 @@ impl FormatNodeRule<StmtReturn> for FormatStmtReturn {
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtTypeAlias;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::statement::stmt_assign::{
|
||||
AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression,
|
||||
};
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtTypeAlias;
|
||||
@@ -41,4 +42,12 @@ impl FormatNodeRule<StmtTypeAlias> for FormatStmtTypeAlias {
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,11 @@ use ruff_formatter::{
|
||||
use ruff_python_ast::helpers::is_compound_statement;
|
||||
use ruff_python_ast::{self as ast, Expr, PySourceType, Stmt, Suite};
|
||||
use ruff_python_ast::{AnyNodeRef, StmtExpr};
|
||||
use ruff_python_trivia::{
|
||||
SimpleTokenKind, SimpleTokenizer, lines_after, lines_after_ignoring_end_of_line_trivia,
|
||||
lines_before,
|
||||
};
|
||||
use ruff_python_trivia::{lines_after, lines_after_ignoring_end_of_line_trivia, lines_before};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::comments::{
|
||||
Comments, LeadingDanglingTrailingComments, has_skip_comment, leading_comments,
|
||||
trailing_comments,
|
||||
Comments, LeadingDanglingTrailingComments, leading_comments, trailing_comments,
|
||||
};
|
||||
use crate::context::{NodeLevel, TopLevelStatementPosition, WithIndentLevel, WithNodeLevel};
|
||||
use crate::other::string_literal::StringLiteralKind;
|
||||
@@ -20,9 +16,9 @@ use crate::prelude::*;
|
||||
use crate::preview::{
|
||||
is_allow_newline_after_block_open_enabled, is_blank_line_before_decorated_class_in_stub_enabled,
|
||||
};
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::statement::stmt_expr::FormatStmtExpr;
|
||||
use crate::verbatim::{
|
||||
write_skipped_statements, write_suppressed_statements_starting_with_leading_comment,
|
||||
suppressed_node, write_suppressed_statements_starting_with_leading_comment,
|
||||
write_suppressed_statements_starting_with_trailing_comment,
|
||||
};
|
||||
|
||||
@@ -156,21 +152,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
|
||||
let first_comments = comments.leading_dangling_trailing(first);
|
||||
|
||||
let (mut preceding, mut empty_line_after_docstring) = if let Some(verbatim_range) =
|
||||
skip_range(first.statement(), iter.as_slice(), f.context())
|
||||
{
|
||||
let preceding =
|
||||
write_skipped_statements(first.statement(), &mut iter, verbatim_range, f)?;
|
||||
|
||||
// Insert a newline after a module level docstring, but treat
|
||||
// it as a docstring otherwise. See: https://github.com/psf/black/pull/3932.
|
||||
let empty_line_after_docstring =
|
||||
matches!(self.kind, SuiteKind::TopLevel | SuiteKind::Class)
|
||||
&& DocstringStmt::try_from_statement(preceding, self.kind, f.context())
|
||||
.is_some();
|
||||
|
||||
(preceding, empty_line_after_docstring)
|
||||
} else if first_comments
|
||||
let (mut preceding, mut empty_line_after_docstring) = if first_comments
|
||||
.leading
|
||||
.iter()
|
||||
.any(|comment| comment.is_suppression_off_comment(source))
|
||||
@@ -409,10 +391,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(verbatim_range) = skip_range(following, iter.as_slice(), f.context()) {
|
||||
preceding = write_skipped_statements(following, &mut iter, verbatim_range, f)?;
|
||||
preceding_comments = comments.leading_dangling_trailing(preceding);
|
||||
} else if following_comments
|
||||
if following_comments
|
||||
.leading
|
||||
.iter()
|
||||
.any(|comment| comment.is_suppression_off_comment(source))
|
||||
@@ -861,57 +840,61 @@ impl Format<PyFormatContext<'_>> for DocstringStmt<'_> {
|
||||
let comments = f.context().comments().clone();
|
||||
let node_comments = comments.leading_dangling_trailing(self.docstring);
|
||||
|
||||
// SAFETY: Safe because `DocStringStmt` guarantees that it only ever wraps a `ExprStmt` containing a `ExprStringLiteral`.
|
||||
let string_literal = self
|
||||
.docstring
|
||||
.as_expr_stmt()
|
||||
.unwrap()
|
||||
.value
|
||||
.as_string_literal_expr()
|
||||
.unwrap();
|
||||
if FormatStmtExpr.is_suppressed(node_comments.trailing, f.context()) {
|
||||
suppressed_node(self.docstring).fmt(f)
|
||||
} else {
|
||||
// SAFETY: Safe because `DocStringStmt` guarantees that it only ever wraps a `ExprStmt` containing a `ExprStringLiteral`.
|
||||
let string_literal = self
|
||||
.docstring
|
||||
.as_expr_stmt()
|
||||
.unwrap()
|
||||
.value
|
||||
.as_string_literal_expr()
|
||||
.unwrap();
|
||||
|
||||
// We format the expression, but the statement carries the comments
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
leading_comments(node_comments.leading),
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
.is_enabled()
|
||||
.then_some(source_position(self.docstring.start())),
|
||||
string_literal
|
||||
.format()
|
||||
.with_options(StringLiteralKind::Docstring),
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
.is_enabled()
|
||||
.then_some(source_position(self.docstring.end())),
|
||||
]
|
||||
)?;
|
||||
// We format the expression, but the statement carries the comments
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
leading_comments(node_comments.leading),
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
.is_enabled()
|
||||
.then_some(source_position(self.docstring.start())),
|
||||
string_literal
|
||||
.format()
|
||||
.with_options(StringLiteralKind::Docstring),
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
.is_enabled()
|
||||
.then_some(source_position(self.docstring.end())),
|
||||
]
|
||||
)?;
|
||||
|
||||
if self.suite_kind == SuiteKind::Class {
|
||||
// Comments after class docstrings need a newline between the docstring and the
|
||||
// comment (https://github.com/astral-sh/ruff/issues/7948).
|
||||
// ```python
|
||||
// class ModuleBrowser:
|
||||
// """Browse module classes and functions in IDLE."""
|
||||
// # ^ Insert a newline above here
|
||||
//
|
||||
// def __init__(self, master, path, *, _htest=False, _utest=False):
|
||||
// pass
|
||||
// ```
|
||||
if let Some(own_line) = node_comments
|
||||
.trailing
|
||||
.iter()
|
||||
.find(|comment| comment.line_position().is_own_line())
|
||||
{
|
||||
if lines_before(own_line.start(), f.context().source()) < 2 {
|
||||
empty_line().fmt(f)?;
|
||||
if self.suite_kind == SuiteKind::Class {
|
||||
// Comments after class docstrings need a newline between the docstring and the
|
||||
// comment (https://github.com/astral-sh/ruff/issues/7948).
|
||||
// ```python
|
||||
// class ModuleBrowser:
|
||||
// """Browse module classes and functions in IDLE."""
|
||||
// # ^ Insert a newline above here
|
||||
//
|
||||
// def __init__(self, master, path, *, _htest=False, _utest=False):
|
||||
// pass
|
||||
// ```
|
||||
if let Some(own_line) = node_comments
|
||||
.trailing
|
||||
.iter()
|
||||
.find(|comment| comment.line_position().is_own_line())
|
||||
{
|
||||
if lines_before(own_line.start(), f.context().source()) < 2 {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trailing_comments(node_comments.trailing).fmt(f)
|
||||
trailing_comments(node_comments.trailing).fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,58 +938,6 @@ impl Format<PyFormatContext<'_>> for SuiteChildStatement<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn skip_range(
|
||||
first: &Stmt,
|
||||
statements: &[Stmt],
|
||||
context: &PyFormatContext,
|
||||
) -> Option<TextRange> {
|
||||
let start = first.start();
|
||||
let mut last_statement = first;
|
||||
|
||||
let comments = context.comments();
|
||||
let source = context.source();
|
||||
|
||||
for statement in statements {
|
||||
if new_logical_line_between_statements(
|
||||
source,
|
||||
TextRange::new(last_statement.end(), statement.start()),
|
||||
) {
|
||||
break;
|
||||
}
|
||||
last_statement = statement;
|
||||
}
|
||||
|
||||
if has_skip_comment(comments.trailing(last_statement), source) {
|
||||
Some(TextRange::new(
|
||||
start,
|
||||
trailing_semicolon(last_statement.into(), source)
|
||||
.map_or_else(|| last_statement.end(), ruff_text_size::TextRange::end),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn new_logical_line_between_statements(source: &str, between_statement_range: TextRange) -> bool {
|
||||
let mut tokenizer = SimpleTokenizer::new(source, between_statement_range).map(|tok| tok.kind());
|
||||
|
||||
while let Some(token) = tokenizer.next() {
|
||||
match token {
|
||||
SimpleTokenKind::Continuation => {
|
||||
tokenizer.next();
|
||||
}
|
||||
SimpleTokenKind::Newline => {
|
||||
return true;
|
||||
}
|
||||
// Since we are between statements, there are
|
||||
// no non-trivia tokens, so there is no need to check
|
||||
// for these and do an early return.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_formatter::format;
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::borrow::Cow;
|
||||
use std::iter::FusedIterator;
|
||||
use std::slice::Iter;
|
||||
|
||||
use itertools::PeekingNext;
|
||||
use ruff_formatter::{FormatError, write};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::Stmt;
|
||||
@@ -452,40 +451,6 @@ fn write_suppressed_statements<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub(crate) fn write_skipped_statements<'a>(
|
||||
first_skipped: &'a Stmt,
|
||||
statements: &mut std::slice::Iter<'a, Stmt>,
|
||||
verbatim_range: TextRange,
|
||||
f: &mut PyFormatter,
|
||||
) -> FormatResult<&'a Stmt> {
|
||||
let comments = f.context().comments().clone();
|
||||
comments.mark_verbatim_node_comments_formatted(first_skipped.into());
|
||||
|
||||
let mut preceding = first_skipped;
|
||||
|
||||
while let Some(prec) = statements.peeking_next(|next| next.end() <= verbatim_range.end()) {
|
||||
comments.mark_verbatim_node_comments_formatted(prec.into());
|
||||
preceding = prec;
|
||||
}
|
||||
|
||||
let first_leading = comments.leading(first_skipped);
|
||||
let preceding_trailing = comments.trailing(preceding);
|
||||
|
||||
// Write the outer comments and format the node as verbatim
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
leading_comments(first_leading),
|
||||
source_position(verbatim_range.start()),
|
||||
verbatim_text(verbatim_range),
|
||||
source_position(verbatim_range.end()),
|
||||
trailing_comments(preceding_trailing)
|
||||
]
|
||||
)?;
|
||||
Ok(preceding)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum InSuppression {
|
||||
No,
|
||||
@@ -928,6 +893,65 @@ impl Format<PyFormatContext<'_>> for VerbatimText {
|
||||
}
|
||||
}
|
||||
|
||||
/// Disables formatting for `node` and instead uses the same formatting as the node has in source.
|
||||
///
|
||||
/// The `node` gets indented as any formatted node to avoid syntax errors when the indentation string changes (e.g. from 2 spaces to 4).
|
||||
/// The `node`s leading and trailing comments are formatted as usual, except if they fall into the suppressed node's range.
|
||||
#[cold]
|
||||
pub(crate) fn suppressed_node<'a, N>(node: N) -> FormatSuppressedNode<'a>
|
||||
where
|
||||
N: Into<AnyNodeRef<'a>>,
|
||||
{
|
||||
FormatSuppressedNode { node: node.into() }
|
||||
}
|
||||
|
||||
pub(crate) struct FormatSuppressedNode<'a> {
|
||||
node: AnyNodeRef<'a>,
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for FormatSuppressedNode<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
|
||||
let comments = f.context().comments().clone();
|
||||
let node_comments = comments.leading_dangling_trailing(self.node);
|
||||
|
||||
// Mark all comments as formatted that fall into the node range
|
||||
for comment in node_comments.leading {
|
||||
if comment.start() > self.node.start() {
|
||||
comment.mark_formatted();
|
||||
}
|
||||
}
|
||||
|
||||
for comment in node_comments.trailing {
|
||||
if comment.start() < self.node.end() {
|
||||
comment.mark_formatted();
|
||||
}
|
||||
}
|
||||
|
||||
// Some statements may end with a semicolon. Preserve the semicolon
|
||||
let semicolon_range = self
|
||||
.node
|
||||
.is_statement()
|
||||
.then(|| trailing_semicolon(self.node, f.context().source()))
|
||||
.flatten();
|
||||
let verbatim_range = semicolon_range.map_or(self.node.range(), |semicolon| {
|
||||
TextRange::new(self.node.start(), semicolon.end())
|
||||
});
|
||||
comments.mark_verbatim_node_comments_formatted(self.node);
|
||||
|
||||
// Write the outer comments and format the node as verbatim
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
leading_comments(node_comments.leading),
|
||||
source_position(verbatim_range.start()),
|
||||
verbatim_text(verbatim_range),
|
||||
source_position(verbatim_range.end()),
|
||||
trailing_comments(node_comments.trailing)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub(crate) fn write_suppressed_clause_header(
|
||||
header: ClauseHeader,
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
class Simple:
|
||||
x=1
|
||||
x=2 # fmt: skip
|
||||
x=3
|
||||
|
||||
class Semicolon:
|
||||
x=1
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class TrailingSemicolon:
|
||||
x=1
|
||||
x=2;x=3 ; # fmt: skip
|
||||
x=4
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
x=1;
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class CompoundInSuite:
|
||||
x=1
|
||||
def foo(): y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x=1
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class MultiLineSkip:
|
||||
x=1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
] # fmt: skip
|
||||
|
||||
class MultiLineSemicolon:
|
||||
x=1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
]; x=2 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAfter:
|
||||
x=1
|
||||
x = ['a']\
|
||||
; y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonBefore:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAndNewline:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
|
||||
y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAndNewlineAndComment:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
# 1
|
||||
y=1 # fmt: skip
|
||||
|
||||
class RepeatedLineContinuation:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
\
|
||||
\
|
||||
y=1 # fmt: skip
|
||||
|
||||
class MultiLineSemicolonComments:
|
||||
x=1
|
||||
# 1
|
||||
x = [ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
# 4
|
||||
]; x=2 # 5 # fmt: skip # 6
|
||||
|
||||
class DocstringSkipped:
|
||||
'''This is a docstring''' # fmt: skip
|
||||
x=1
|
||||
|
||||
class MultilineDocstringSkipped:
|
||||
'''This is a docstring
|
||||
''' # fmt: skip
|
||||
x=1
|
||||
|
||||
class FirstStatementNewlines:
|
||||
|
||||
|
||||
|
||||
|
||||
x=1 # fmt: skip
|
||||
|
||||
class ChainingSemicolons:
|
||||
x=[
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
];x=1;x=[
|
||||
'1',
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # fmt: skip
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
class MixingCompound:
|
||||
def foo(): bar(); import zoo # fmt: skip
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/17331
|
||||
def main() -> None:
|
||||
import ipdb; ipdb.set_trace() # noqa: E402,E702,I001 # fmt: skip
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/11430
|
||||
print(); print() # noqa # fmt: skip
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
class Simple:
|
||||
x = 1
|
||||
x=2 # fmt: skip
|
||||
x = 3
|
||||
|
||||
|
||||
class Semicolon:
|
||||
x = 1
|
||||
x=2;x=3 # fmt: skip
|
||||
x = 4
|
||||
|
||||
|
||||
class TrailingSemicolon:
|
||||
x = 1
|
||||
x=2;x=3 ; # fmt: skip
|
||||
x = 4
|
||||
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
x = 1
|
||||
x=2;x=3 # fmt: skip
|
||||
x = 4
|
||||
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x = 1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x = 5
|
||||
|
||||
|
||||
class CompoundInSuite:
|
||||
x = 1
|
||||
|
||||
def foo(): y=1 # fmt: skip
|
||||
|
||||
x = 2
|
||||
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x = 1
|
||||
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
|
||||
x = 2
|
||||
|
||||
|
||||
class MultiLineSkip:
|
||||
x = 1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
class MultiLineSemicolon:
|
||||
x = 1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
]; x=2 # fmt: skip
|
||||
|
||||
|
||||
class LineContinuationSemicolonAfter:
|
||||
x = 1
|
||||
x = ['a']\
|
||||
; y=1 # fmt: skip
|
||||
|
||||
|
||||
class LineContinuationSemicolonBefore:
|
||||
x = 1
|
||||
x = ['a']; \
|
||||
y=1 # fmt: skip
|
||||
|
||||
|
||||
class LineContinuationSemicolonAndNewline:
|
||||
x = 1
|
||||
x = ["a"]
|
||||
y=1 # fmt: skip
|
||||
|
||||
|
||||
class LineContinuationSemicolonAndNewlineAndComment:
|
||||
x = 1
|
||||
x = ["a"]
|
||||
# 1
|
||||
y=1 # fmt: skip
|
||||
|
||||
|
||||
class RepeatedLineContinuation:
|
||||
x = 1
|
||||
x = ['a']; \
|
||||
\
|
||||
\
|
||||
y=1 # fmt: skip
|
||||
|
||||
|
||||
class MultiLineSemicolonComments:
|
||||
x = 1
|
||||
# 1
|
||||
x = [ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
# 4
|
||||
]; x=2 # 5 # fmt: skip # 6
|
||||
|
||||
|
||||
class DocstringSkipped:
|
||||
'''This is a docstring''' # fmt: skip
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
class MultilineDocstringSkipped:
|
||||
'''This is a docstring
|
||||
''' # fmt: skip
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
class FirstStatementNewlines:
|
||||
x=1 # fmt: skip
|
||||
|
||||
|
||||
class ChainingSemicolons:
|
||||
x=[
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
];x=1;x=[
|
||||
'1',
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # fmt: skip
|
||||
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
|
||||
class MixingCompound:
|
||||
def foo(): bar(); import zoo # fmt: skip
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/17331
|
||||
def main() -> None:
|
||||
import ipdb; ipdb.set_trace() # noqa: E402,E702,I001 # fmt: skip
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/11430
|
||||
print(); print() # noqa # fmt: skip
|
||||
```
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
# 0
|
||||
'''Module docstring
|
||||
multiple lines''' # 1 # fmt: skip # 2
|
||||
|
||||
import a;import b; from c import (
|
||||
x,y,z
|
||||
); import f # fmt: skip
|
||||
|
||||
x=1;x=2;x=3;x=4 # fmt: skip
|
||||
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
def foo(): x=[
|
||||
'1',
|
||||
'2',
|
||||
];x=1 # fmt: skip
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
# 0
|
||||
'''Module docstring
|
||||
multiple lines''' # 1 # fmt: skip # 2
|
||||
|
||||
import a;import b; from c import (
|
||||
x,y,z
|
||||
); import f # fmt: skip
|
||||
|
||||
x=1;x=2;x=3;x=4 # fmt: skip
|
||||
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
|
||||
def foo():
|
||||
x=[
|
||||
'1',
|
||||
'2',
|
||||
];x=1 # fmt: skip
|
||||
```
|
||||
@@ -1,147 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
class Simple:
|
||||
# Range comprises skip range
|
||||
x=1
|
||||
<RANGE_START>x=2 <RANGE_END># fmt: skip
|
||||
x=3
|
||||
|
||||
class Semicolon:
|
||||
# Range is part of skip range
|
||||
x=1
|
||||
x=2;<RANGE_START>x=3<RANGE_END> # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatFirst:
|
||||
x=1
|
||||
<RANGE_START>x=2<RANGE_END>;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatMiddle:
|
||||
x=1
|
||||
x=2;<RANGE_START>x=3<RANGE_END>;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on right side
|
||||
<RANGE_START>x=1;
|
||||
x=2<RANGE_END>;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on left side
|
||||
x=1;
|
||||
x=2;<RANGE_START>x=3 # fmt: skip
|
||||
x=4<RANGE_END>
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class CompoundInSuite:
|
||||
x=1
|
||||
<RANGE_START>def foo(): y=1 <RANGE_END># fmt: skip
|
||||
x=2
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x=1
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class MultiLineSkip:
|
||||
# Range inside statement
|
||||
x=1
|
||||
x = <RANGE_START>[
|
||||
'1',
|
||||
'2',<RANGE_END>
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3<RANGE_START>
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4<RANGE_END> # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
class Simple:
|
||||
# Range comprises skip range
|
||||
x=1
|
||||
x=2 # fmt: skip
|
||||
x=3
|
||||
|
||||
class Semicolon:
|
||||
# Range is part of skip range
|
||||
x=1
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatFirst:
|
||||
x=1
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatMiddle:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on right side
|
||||
x = 1
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on left side
|
||||
x=1;
|
||||
x=2;x=3 # fmt: skip
|
||||
x = 4
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class CompoundInSuite:
|
||||
x=1
|
||||
def foo(): y=1 # fmt: skip
|
||||
|
||||
x=2
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x=1
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class MultiLineSkip:
|
||||
# Range inside statement
|
||||
x=1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
```
|
||||
@@ -30,8 +30,7 @@ mypy_primer \
|
||||
--project-selector '/black$'
|
||||
```
|
||||
|
||||
This will show the diagnostics diff for the `black` project between the `main` branch and your `my/feature` branch. To run the
|
||||
diff for all projects we currently enable in CI, use `--project-selector "/($(paste -s -d'|' crates/ty_python_semantic/resources/primer/good.txt))\$"`.
|
||||
This will show the diagnostics diff for the `black` project between the `main` branch and your `my/feature` branch.
|
||||
|
||||
You can also take a look at the [full list of ecosystem projects]. Note that some of them might still need a `ty_paths` configuration
|
||||
option to work correctly.
|
||||
|
||||
@@ -1032,125 +1032,4 @@ reveal_type(asdict(p)) # revealed: dict[str, Any]
|
||||
reveal_type(replace(p, name="Bob")) # revealed: Person
|
||||
```
|
||||
|
||||
## Calling decorator function directly with a class argument
|
||||
|
||||
When a function decorated with `@dataclass_transform()` is called directly with a class argument
|
||||
(not used as a decorator), it should return the class with the dataclass transformation applied.
|
||||
|
||||
### Basic case
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass[T](cls: type[T]) -> type[T]:
|
||||
return cls
|
||||
|
||||
class A:
|
||||
x: int
|
||||
|
||||
B = my_dataclass(A)
|
||||
|
||||
reveal_type(B) # revealed: <class 'A'>
|
||||
|
||||
B(1)
|
||||
```
|
||||
|
||||
### Function with additional parameters
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass[T](cls: type[T], *, order: bool = False) -> type[T]:
|
||||
return cls
|
||||
|
||||
class A:
|
||||
x: int
|
||||
|
||||
B = my_dataclass(A, order=True)
|
||||
|
||||
reveal_type(B) # revealed: <class 'A'>
|
||||
|
||||
reveal_type(B(1) < B(2)) # revealed: bool
|
||||
```
|
||||
|
||||
### Overloaded decorator function
|
||||
|
||||
When the decorator function has overloads (one for direct class application, one for returning a
|
||||
decorator), calling it with a class should return the class type.
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Callable, overload
|
||||
|
||||
@overload
|
||||
@dataclass_transform()
|
||||
def my_dataclass[T](cls: type[T]) -> type[T]: ...
|
||||
@overload
|
||||
def my_dataclass[T]() -> Callable[[type[T]], type[T]]: ...
|
||||
def my_dataclass[T](cls: type[T] | None = None) -> type[T] | Callable[[type[T]], type[T]]:
|
||||
raise NotImplementedError
|
||||
|
||||
class A:
|
||||
x: int
|
||||
|
||||
B = my_dataclass(A)
|
||||
|
||||
reveal_type(B) # revealed: <class 'A'>
|
||||
|
||||
B(1)
|
||||
```
|
||||
|
||||
### Passing a specialized generic class
|
||||
|
||||
When calling a `@dataclass_transform()` decorated function with a specialized generic class, the
|
||||
specialization should be preserved.
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass[T](cls: type[T]) -> type[T]:
|
||||
return cls
|
||||
|
||||
class A[T]:
|
||||
x: T
|
||||
|
||||
B = my_dataclass(A[int])
|
||||
|
||||
reveal_type(B) # revealed: <class 'A[int]'>
|
||||
|
||||
B(1)
|
||||
```
|
||||
|
||||
### Decorator factory with class parameter
|
||||
|
||||
When a `@dataclass_transform()` decorated function takes a class as a parameter but is used as a
|
||||
decorator factory (returns a decorator), the dataclass behavior should be applied to the decorated
|
||||
class, not to the parameter class.
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
def hydrated_dataclass[T](target: type[T], *, frozen: bool = False):
|
||||
def decorator[U](cls: type[U]) -> type[U]:
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
class Target:
|
||||
pass
|
||||
|
||||
decorator = hydrated_dataclass(Target)
|
||||
reveal_type(decorator) # revealed: <decorator produced by dataclass-like function>
|
||||
|
||||
@hydrated_dataclass(Target)
|
||||
class Model:
|
||||
x: int
|
||||
|
||||
# Model should be a dataclass-like class with x as a field
|
||||
Model(x=1)
|
||||
reveal_type(Model.__init__) # revealed: (self: Model, x: int) -> None
|
||||
```
|
||||
|
||||
[`typing.dataclass_transform`]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform
|
||||
|
||||
@@ -1682,7 +1682,9 @@ def sequence4(cls: type) -> type:
|
||||
class Foo: ...
|
||||
|
||||
ordered_foo = dataclass(order=True)(Foo)
|
||||
reveal_type(ordered_foo) # revealed: <class 'Foo'>
|
||||
reveal_type(ordered_foo()) # revealed: Foo
|
||||
reveal_type(ordered_foo() < ordered_foo()) # revealed: bool
|
||||
reveal_type(ordered_foo) # revealed: type[Foo] & Any
|
||||
# TODO: should be `Foo & Any`
|
||||
reveal_type(ordered_foo()) # revealed: @Todo(Type::Intersection.call)
|
||||
# TODO: should be `Any`
|
||||
reveal_type(ordered_foo() < ordered_foo()) # revealed: @Todo(Type::Intersection.call)
|
||||
```
|
||||
|
||||
@@ -352,16 +352,15 @@ def two_final_constrained[T: (FinalClass, AnotherFinalClass), U: (FinalClass, An
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
```
|
||||
|
||||
A bound or constrained typevar is not a subtype of itself in a union, since subtyping between a
|
||||
TypeVar and an arbitrary other type cannot be guaranteed to be reflexive.
|
||||
A bound or constrained typevar is a subtype of itself in a union:
|
||||
|
||||
```py
|
||||
def union[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
|
||||
static_assert(is_assignable_to(T, T | None))
|
||||
static_assert(is_assignable_to(U, U | None))
|
||||
|
||||
static_assert(not is_subtype_of(T, T | None))
|
||||
static_assert(not is_subtype_of(U, U | None))
|
||||
static_assert(is_subtype_of(T, T | None))
|
||||
static_assert(is_subtype_of(U, U | None))
|
||||
```
|
||||
|
||||
A bound or constrained typevar in a union with a dynamic type is assignable to the typevar:
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
spark # too many iterations (in `exported_names` query), `should not be able to access instance member `spark` of type variable IndexOpsLike@astype in inferable position`
|
||||
steam.py # too many iterations
|
||||
@@ -1,154 +0,0 @@
|
||||
AutoSplit
|
||||
DateType
|
||||
Expression
|
||||
PyGithub
|
||||
PyWinCtl
|
||||
Tanjun
|
||||
aiohttp
|
||||
aiohttp-devtools
|
||||
aioredis
|
||||
aiortc
|
||||
alectryon
|
||||
alerta
|
||||
altair
|
||||
antidote
|
||||
anyio
|
||||
apprise
|
||||
archinstall
|
||||
artigraph
|
||||
arviz
|
||||
async-utils
|
||||
asynq
|
||||
attrs
|
||||
bandersnatch
|
||||
beartype
|
||||
bidict
|
||||
black
|
||||
bokeh
|
||||
boostedblob
|
||||
build
|
||||
check-jsonschema
|
||||
cibuildwheel
|
||||
cki-lib
|
||||
cloud-init
|
||||
colour
|
||||
com2ann
|
||||
comtypes
|
||||
core
|
||||
cpython
|
||||
cryptography
|
||||
cwltool
|
||||
dacite
|
||||
dd-trace-py
|
||||
dedupe
|
||||
discord.py
|
||||
django-stubs
|
||||
django-test-migrations
|
||||
downforeveryone
|
||||
dragonchain
|
||||
dulwich
|
||||
egglog-python
|
||||
flake8
|
||||
flake8-pyi
|
||||
freqtrade
|
||||
git-revise
|
||||
graphql-core
|
||||
httpx-caching
|
||||
hydpy
|
||||
hydra-zen
|
||||
ibis
|
||||
ignite
|
||||
imagehash
|
||||
isort
|
||||
itsdangerous
|
||||
janus
|
||||
jax
|
||||
jinja
|
||||
koda-validate
|
||||
kopf
|
||||
kornia
|
||||
manticore
|
||||
materialize
|
||||
meson
|
||||
mitmproxy
|
||||
mkdocs
|
||||
mkosi
|
||||
mongo-python-driver
|
||||
more-itertools
|
||||
mypy
|
||||
mypy-protobuf
|
||||
mypy_primer
|
||||
nionutils
|
||||
nox
|
||||
numpy-stl
|
||||
openlibrary
|
||||
operator
|
||||
optuna
|
||||
paasta
|
||||
packaging
|
||||
pandas
|
||||
pandas-stubs
|
||||
pandera
|
||||
paroxython
|
||||
parso
|
||||
pegen
|
||||
pip
|
||||
poetry
|
||||
porcupine
|
||||
ppb-vector
|
||||
prefect
|
||||
psycopg
|
||||
pwndbg
|
||||
pybind11
|
||||
pycryptodome
|
||||
pydantic
|
||||
pyinstrument
|
||||
pyjwt
|
||||
pylint
|
||||
pylox
|
||||
pyodide
|
||||
pyp
|
||||
pyppeteer
|
||||
pyproject-metadata
|
||||
pytest
|
||||
pytest-robotframework
|
||||
python-chess
|
||||
python-htmlgen
|
||||
python-sop
|
||||
pywin32
|
||||
rclip
|
||||
rich
|
||||
rotki
|
||||
schema_salad
|
||||
schemathesis
|
||||
scikit-build-core
|
||||
scikit-learn
|
||||
scipy
|
||||
scipy-stubs
|
||||
scrapy
|
||||
setuptools
|
||||
sockeye
|
||||
spack
|
||||
speedrun.com_global_scoreboard_webapp
|
||||
sphinx
|
||||
starlette
|
||||
static-frame
|
||||
stone
|
||||
strawberry
|
||||
streamlit
|
||||
svcs
|
||||
sympy
|
||||
tornado
|
||||
trio
|
||||
twine
|
||||
typeshed-stats
|
||||
urllib3
|
||||
vision
|
||||
websockets
|
||||
werkzeug
|
||||
xarray
|
||||
xarray-dataclasses
|
||||
yarl
|
||||
zipp
|
||||
zope.interface
|
||||
zulip
|
||||
@@ -1833,7 +1833,7 @@ impl<'db> Type<'db> {
|
||||
///
|
||||
/// This method may have false negatives, but it should not have false positives. It should be
|
||||
/// a cheap shallow check, not an exhaustive recursive check.
|
||||
const fn subtyping_is_always_reflexive(self) -> bool {
|
||||
fn subtyping_is_always_reflexive(self) -> bool {
|
||||
match self {
|
||||
Type::Never
|
||||
| Type::FunctionLiteral(..)
|
||||
|
||||
@@ -43,8 +43,8 @@ use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Paramete
|
||||
use crate::types::tuple::{TupleLength, TupleSpec, TupleType};
|
||||
use crate::types::{
|
||||
BoundMethodType, BoundTypeVarIdentity, BoundTypeVarInstance, CallableSignature, CallableType,
|
||||
CallableTypeKind, DATACLASS_FLAGS, DataclassFlags, DataclassParams, FieldInstance,
|
||||
GenericAlias, KnownBoundMethodType, KnownClass, KnownInstanceType, MemberLookupPolicy,
|
||||
CallableTypeKind, ClassLiteral, DATACLASS_FLAGS, DataclassFlags, DataclassParams,
|
||||
FieldInstance, KnownBoundMethodType, KnownClass, KnownInstanceType, MemberLookupPolicy,
|
||||
NominalInstanceType, PropertyInstanceType, SpecialFormType, TrackedConstraintSet,
|
||||
TypeAliasType, TypeContext, TypeVarVariance, UnionBuilder, UnionType, WrapperDescriptorKind,
|
||||
enums, list_members, todo_type,
|
||||
@@ -611,25 +611,6 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
Type::DataclassDecorator(params) => match overload.parameter_types() {
|
||||
[Some(Type::ClassLiteral(class_literal))] => {
|
||||
overload.set_return_type(Type::from(
|
||||
class_literal.with_dataclass_params(db, Some(params)),
|
||||
));
|
||||
}
|
||||
[Some(Type::GenericAlias(generic_alias))] => {
|
||||
let new_origin = generic_alias
|
||||
.origin(db)
|
||||
.with_dataclass_params(db, Some(params));
|
||||
overload.set_return_type(Type::GenericAlias(GenericAlias::new(
|
||||
db,
|
||||
new_origin,
|
||||
generic_alias.specialization(db),
|
||||
)));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
Type::BoundMethod(bound_method)
|
||||
if bound_method.self_instance(db).is_property_instance() =>
|
||||
{
|
||||
@@ -1138,9 +1119,17 @@ impl<'db> Bindings<'db> {
|
||||
overload.parameter_types()
|
||||
{
|
||||
let params = DataclassParams::default_params(db);
|
||||
overload.set_return_type(Type::from(
|
||||
class_literal.with_dataclass_params(db, Some(params)),
|
||||
));
|
||||
overload.set_return_type(Type::from(ClassLiteral::new(
|
||||
db,
|
||||
class_literal.name(db),
|
||||
class_literal.body_scope(db),
|
||||
class_literal.known(db),
|
||||
class_literal.deprecated(db),
|
||||
class_literal.type_check_only(db),
|
||||
Some(params),
|
||||
class_literal.dataclass_transformer_params(db),
|
||||
class_literal.total_ordering(db),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1196,102 +1185,44 @@ impl<'db> Bindings<'db> {
|
||||
// Ideally, either the implementation, or exactly one of the overloads
|
||||
// of the function can have the dataclass_transform decorator applied.
|
||||
// However, we do not yet enforce this, and in the case of multiple
|
||||
// applications of the decorator, we will only consider the last one.
|
||||
let transformer_params = function_type
|
||||
// applications of the decorator, we will only consider the last one
|
||||
// for the return value, since the prior ones will be over-written.
|
||||
let return_type = function_type
|
||||
.iter_overloads_and_implementation(db)
|
||||
.rev()
|
||||
.find_map(|function_overload| {
|
||||
function_overload.dataclass_transformer_params(db)
|
||||
});
|
||||
.filter_map(|function_overload| {
|
||||
function_overload.dataclass_transformer_params(db).map(
|
||||
|params| {
|
||||
// This is a call to a custom function that was decorated with `@dataclass_transformer`.
|
||||
// If this function was called with a keyword argument like `order=False`, we extract
|
||||
// the argument type and overwrite the corresponding flag in `dataclass_params` after
|
||||
// constructing them from the `dataclass_transformer`-parameter defaults.
|
||||
|
||||
if let Some(params) = transformer_params {
|
||||
// If this function was called with a keyword argument like
|
||||
// `order=False`, we extract the argument type and overwrite
|
||||
// the corresponding flag in `dataclass_params`.
|
||||
let dataclass_params =
|
||||
DataclassParams::from_transformer_params(db, params);
|
||||
let mut flags = dataclass_params.flags(db);
|
||||
let dataclass_params =
|
||||
DataclassParams::from_transformer_params(
|
||||
db, params,
|
||||
);
|
||||
let mut flags = dataclass_params.flags(db);
|
||||
|
||||
for (param, flag) in DATACLASS_FLAGS {
|
||||
if let Ok(Some(Type::BooleanLiteral(value))) =
|
||||
overload.parameter_type_by_name(param, false)
|
||||
{
|
||||
flags.set(*flag, value);
|
||||
}
|
||||
}
|
||||
for (param, flag) in DATACLASS_FLAGS {
|
||||
if let Ok(Some(Type::BooleanLiteral(value))) =
|
||||
overload.parameter_type_by_name(param, false)
|
||||
{
|
||||
flags.set(*flag, value);
|
||||
}
|
||||
}
|
||||
|
||||
let dataclass_params = DataclassParams::new(
|
||||
db,
|
||||
flags,
|
||||
dataclass_params.field_specifiers(db),
|
||||
);
|
||||
Type::DataclassDecorator(DataclassParams::new(
|
||||
db,
|
||||
flags,
|
||||
dataclass_params.field_specifiers(db),
|
||||
))
|
||||
},
|
||||
)
|
||||
})
|
||||
.last();
|
||||
|
||||
// The dataclass_transform spec doesn't clarify how to tell whether
|
||||
// a decorated function is a decorator or a decorator factory. We
|
||||
// use heuristics based on the number and type of positional arguments:
|
||||
//
|
||||
// - Zero positional arguments: assume it's a decorator factory.
|
||||
// - More than one positional argument: assume it's a decorator factory.
|
||||
// - Exactly one positional argument that's a class: ambiguous, so check
|
||||
// the return type to disambiguate (class-like means decorate directly).
|
||||
let mut positional_args = overload
|
||||
.signature
|
||||
.parameters()
|
||||
.iter()
|
||||
.zip(overload.parameter_types())
|
||||
.filter(|(param, ty)| ty.is_some() && !param.is_keyword_only())
|
||||
.map(|(_, ty)| ty);
|
||||
|
||||
let first_positional = positional_args.next();
|
||||
let has_more = positional_args.next().is_some();
|
||||
|
||||
// Only attempt direct decoration if exactly one positional argument.
|
||||
if !has_more {
|
||||
// Helper to check if return type is class-like.
|
||||
let returns_class = || {
|
||||
matches!(
|
||||
overload.return_type(),
|
||||
Type::ClassLiteral(_)
|
||||
| Type::GenericAlias(_)
|
||||
| Type::SubclassOf(_)
|
||||
)
|
||||
};
|
||||
|
||||
match first_positional {
|
||||
Some(Some(Type::ClassLiteral(class_literal)))
|
||||
if returns_class() =>
|
||||
{
|
||||
overload.set_return_type(Type::from(
|
||||
class_literal.with_dataclass_params(
|
||||
db,
|
||||
Some(dataclass_params),
|
||||
),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
Some(Some(Type::GenericAlias(generic_alias)))
|
||||
if returns_class() =>
|
||||
{
|
||||
let new_origin = generic_alias
|
||||
.origin(db)
|
||||
.with_dataclass_params(db, Some(dataclass_params));
|
||||
overload.set_return_type(Type::GenericAlias(
|
||||
GenericAlias::new(
|
||||
db,
|
||||
new_origin,
|
||||
generic_alias.specialization(db),
|
||||
),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Zero or more than one positional argument, or the argument is
|
||||
// not a class: assume it's a decorator factory.
|
||||
overload
|
||||
.set_return_type(Type::DataclassDecorator(dataclass_params));
|
||||
if let Some(return_type) = return_type {
|
||||
overload.set_return_type(return_type);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1555,25 +1555,6 @@ impl<'db> ClassLiteral<'db> {
|
||||
self.is_known(db, KnownClass::Tuple)
|
||||
}
|
||||
|
||||
/// Returns a new `ClassLiteral` with the given dataclass params, preserving all other fields.
|
||||
pub(crate) fn with_dataclass_params(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
dataclass_params: Option<DataclassParams<'db>>,
|
||||
) -> Self {
|
||||
ClassLiteral::new(
|
||||
db,
|
||||
self.name(db).clone(),
|
||||
self.body_scope(db),
|
||||
self.known(db),
|
||||
self.deprecated(db),
|
||||
self.type_check_only(db),
|
||||
dataclass_params,
|
||||
self.dataclass_transformer_params(db),
|
||||
self.total_ordering(db),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if this class defines any ordering method (`__lt__`, `__le__`, `__gt__`,
|
||||
/// `__ge__`) in its own body (not inherited). Used by `@total_ordering` to determine if
|
||||
/// synthesis is valid.
|
||||
|
||||
@@ -748,9 +748,9 @@ impl<'db> FunctionLiteral<'db> {
|
||||
fn iter_overloads_and_implementation(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
) -> impl DoubleEndedIterator<Item = OverloadLiteral<'db>> + 'db {
|
||||
let (overloads, implementation) = self.overloads_and_implementation(db);
|
||||
overloads.iter().copied().chain(implementation)
|
||||
) -> impl Iterator<Item = OverloadLiteral<'db>> + 'db {
|
||||
let (implementation, overloads) = self.overloads_and_implementation(db);
|
||||
overloads.into_iter().chain(implementation.iter().copied())
|
||||
}
|
||||
|
||||
/// Typed externally-visible signature for this function.
|
||||
@@ -1034,7 +1034,7 @@ impl<'db> FunctionType<'db> {
|
||||
pub(crate) fn iter_overloads_and_implementation(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
) -> impl DoubleEndedIterator<Item = OverloadLiteral<'db>> + 'db {
|
||||
) -> impl Iterator<Item = OverloadLiteral<'db>> + 'db {
|
||||
self.literal(db).iter_overloads_and_implementation(db)
|
||||
}
|
||||
|
||||
|
||||
@@ -2941,7 +2941,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// params of the last seen usage of `@dataclass_transform`
|
||||
let transformer_params = f
|
||||
.iter_overloads_and_implementation(self.db())
|
||||
.rev()
|
||||
.find_map(|overload| overload.dataclass_transformer_params(self.db()));
|
||||
if let Some(transformer_params) = transformer_params {
|
||||
dataclass_params = Some(DataclassParams::from_transformer_params(
|
||||
|
||||
@@ -195,17 +195,6 @@ impl TypeRelation<'_> {
|
||||
pub(crate) const fn is_subtyping(self) -> bool {
|
||||
matches!(self, TypeRelation::Subtyping)
|
||||
}
|
||||
|
||||
pub(crate) const fn can_safely_assume_reflexivity(self, ty: Type) -> bool {
|
||||
match self {
|
||||
TypeRelation::Assignability
|
||||
| TypeRelation::ConstraintSetAssignability
|
||||
| TypeRelation::Redundancy => true,
|
||||
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => {
|
||||
ty.subtyping_is_always_reflexive()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
@@ -340,7 +329,7 @@ impl<'db> Type<'db> {
|
||||
//
|
||||
// Note that we could do a full equivalence check here, but that would be both expensive
|
||||
// and unnecessary. This early return is only an optimisation.
|
||||
if relation.can_safely_assume_reflexivity(self) && self == target {
|
||||
if (!relation.is_subtyping() || self.subtyping_is_always_reflexive()) && self == target {
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
@@ -471,38 +460,29 @@ impl<'db> Type<'db> {
|
||||
},
|
||||
}),
|
||||
|
||||
// In general, a TypeVar `T` is not redundant with a type `S` unless one of the two conditions is satisfied:
|
||||
// In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied:
|
||||
// 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`.
|
||||
// TypeVars without an explicit upper bound are treated as having an implicit upper bound of `object`.
|
||||
// 2. `T` is a constrained TypeVar and all of `T`'s constraints are subtypes of `S`.
|
||||
//
|
||||
// However, there is one exception to this general rule: for any given typevar `T`,
|
||||
// `T` will always be a subtype of any union containing `T`.
|
||||
(_, Type::Union(union))
|
||||
if relation.can_safely_assume_reflexivity(self)
|
||||
(Type::TypeVar(bound_typevar), Type::Union(union))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& union.elements(db).contains(&self) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
// A similar rule applies in reverse to intersection types.
|
||||
(Type::Intersection(intersection), _)
|
||||
if relation.can_safely_assume_reflexivity(target)
|
||||
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.positive(db).contains(&target) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
(Type::Intersection(intersection), _)
|
||||
if relation.is_assignability()
|
||||
&& intersection.positive(db).iter().any(Type::is_dynamic) =>
|
||||
{
|
||||
// If the intersection contains `Any`/`Unknown`/`@Todo`, it is assignable to any type.
|
||||
// `Any` could materialize to `Never`, `Never & T & ~S` simplifies to `Never` for any
|
||||
// `T` and any `S`, and `Never` is a subtype of all types.
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
(Type::Intersection(intersection), _)
|
||||
if relation.can_safely_assume_reflexivity(target)
|
||||
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.negative(db).contains(&target) =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
|
||||
@@ -265,29 +265,26 @@ Instead, apply the `# fmt: off` comment to the entire statement:
|
||||
Like Black, Ruff will _also_ recognize [YAPF](https://github.com/google/yapf)'s `# yapf: disable` and `# yapf: enable` pragma
|
||||
comments, which are treated equivalently to `# fmt: off` and `# fmt: on`, respectively.
|
||||
|
||||
`# fmt: skip` comments suppress formatting for a case header, decorator,
|
||||
function definition, class definition, or the preceding statements
|
||||
on the same logical line. The formatter leaves the following unchanged:
|
||||
`# fmt: skip` comments suppress formatting for a preceding statement, case header, decorator,
|
||||
function definition, or class definition:
|
||||
|
||||
```python
|
||||
if True:
|
||||
pass
|
||||
elif False: # fmt: skip
|
||||
elif False: # fmt: skip
|
||||
pass
|
||||
|
||||
@Test
|
||||
@Test2(a,b) # fmt: skip
|
||||
@Test2 # fmt: skip
|
||||
def test(): ...
|
||||
|
||||
a = [1,2,3,4,5] # fmt: skip
|
||||
a = [1, 2, 3, 4, 5] # fmt: skip
|
||||
|
||||
def test(a,b,c,d,e,f) -> int: # fmt: skip
|
||||
def test(a, b, c, d, e, f) -> int: # fmt: skip
|
||||
pass
|
||||
|
||||
x=1;x=2;x=3 # fmt: skip
|
||||
```
|
||||
|
||||
Adding a `# fmt: skip` comment at the end of an expression will have no effect. In
|
||||
As such, adding an `# fmt: skip` comment at the end of an expression will have no effect. In
|
||||
the following example, the list entry `'1'` will be formatted, despite the `# fmt: skip`:
|
||||
|
||||
```python
|
||||
|
||||
@@ -5,8 +5,6 @@ echo "Enabling mypy primer specific configuration overloads (see .github/mypy-pr
|
||||
mkdir -p ~/.config/ty
|
||||
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
|
||||
|
||||
PRIMER_SELECTOR="$(paste -s -d'|' "${PRIMER_SELECTOR}")"
|
||||
|
||||
echo "new commit"
|
||||
git rev-list --format=%s --max-count=1 "${GITHUB_SHA}"
|
||||
|
||||
@@ -19,7 +17,6 @@ git rev-list --format=%s --max-count=1 base_commit
|
||||
|
||||
cd ..
|
||||
|
||||
echo "Project selector: ${PRIMER_SELECTOR}"
|
||||
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
||||
uvx \
|
||||
--from="git+https://github.com/hauntsaninja/mypy_primer@ea7c45364a129b9a556b08a6285f8df0e5aedf0a" \
|
||||
@@ -29,7 +26,6 @@ uvx \
|
||||
--cargo-profile profiling \
|
||||
--old base_commit \
|
||||
--new "${GITHUB_SHA}" \
|
||||
--project-selector "/($PRIMER_SELECTOR)\$" \
|
||||
--output concise \
|
||||
--debug > "${DIFF_FILE}" || [ $? -eq 1 ]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user