Skip to content

Commit e47e3f4

Browse files
committed
Merge branch 'main' into dcreager/typing-any
* main: [red-knot] Test: Hashable/Sized => A/B (#14769) [`flake8-type-checking`] Expands TC006 docs to better explain itself (#14749) [`pycodestyle`] Handle f-strings properly for `invalid-escape-sequence (W605)` (#14748) [red-knot] Add fuzzer to catch panics for invalid syntax (#14678) Check `AIR001` from builtin or providers `operators` module (#14631) [airflow]: extend removed names (AIR302) (#14734)
2 parents 0ed09b6 + 948549f commit e47e3f4

19 files changed

+722
-167
lines changed

.github/workflows/ci.yaml

+8-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ jobs:
3232
# Flag that is raised when any code is changed
3333
# This is superset of the linter and formatter
3434
code: ${{ steps.changed.outputs.code_any_changed }}
35+
# Flag that is raised when any code that affects the fuzzer is changed
36+
fuzz: ${{ steps.changed.outputs.fuzz_any_changed }}
3537
steps:
3638
- uses: actions/checkout@v4
3739
with:
@@ -79,6 +81,11 @@ jobs:
7981
- python/**
8082
- .github/workflows/ci.yaml
8183
84+
fuzz:
85+
- fuzz/Cargo.toml
86+
- fuzz/Cargo.lock
87+
- fuzz/fuzz_targets/**
88+
8289
code:
8390
- "**/*"
8491
- "!**/*.md"
@@ -288,7 +295,7 @@ jobs:
288295
name: "cargo fuzz build"
289296
runs-on: ubuntu-latest
290297
needs: determine_changes
291-
if: ${{ github.ref == 'refs/heads/main' }}
298+
if: ${{ github.ref == 'refs/heads/main' || needs.determine_changes.outputs.fuzz == 'true' }}
292299
timeout-minutes: 10
293300
steps:
294301
- uses: actions/checkout@v4

crates/red_knot_python_semantic/src/types/builder.rs

+62-37
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,6 @@ mod tests {
378378
use crate::db::tests::TestDb;
379379
use crate::program::{Program, SearchPathSettings};
380380
use crate::python_version::PythonVersion;
381-
use crate::stdlib::typing_symbol;
382381
use crate::types::{global_symbol, todo_type, KnownClass, UnionBuilder};
383382
use crate::ProgramSettings;
384383
use ruff_db::files::system_path_to_file;
@@ -626,59 +625,85 @@ mod tests {
626625

627626
#[test]
628627
fn intersection_negation_distributes_over_union() {
629-
let db = setup_db();
630-
let st = typing_symbol(&db, "Sized").expect_type().to_instance(&db);
631-
let ht = typing_symbol(&db, "Hashable")
628+
let mut db = setup_db();
629+
db.write_dedented(
630+
"/src/module.py",
631+
r#"
632+
class A: ...
633+
class B: ...
634+
"#,
635+
)
636+
.unwrap();
637+
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();
638+
639+
let a = global_symbol(&db, module, "A")
640+
.expect_type()
641+
.to_instance(&db);
642+
let b = global_symbol(&db, module, "B")
632643
.expect_type()
633644
.to_instance(&db);
634-
// sh_t: Sized & Hashable
635-
let sh_t = IntersectionBuilder::new(&db)
636-
.add_positive(st)
637-
.add_positive(ht)
645+
646+
// intersection: A & B
647+
let intersection = IntersectionBuilder::new(&db)
648+
.add_positive(a)
649+
.add_positive(b)
638650
.build()
639651
.expect_intersection();
640-
assert_eq!(sh_t.pos_vec(&db), &[st, ht]);
641-
assert_eq!(sh_t.neg_vec(&db), &[]);
652+
assert_eq!(intersection.pos_vec(&db), &[a, b]);
653+
assert_eq!(intersection.neg_vec(&db), &[]);
642654

643-
// ~sh_t => ~Sized | ~Hashable
644-
let not_s_h_t = IntersectionBuilder::new(&db)
645-
.add_negative(Type::Intersection(sh_t))
655+
// ~intersection => ~A | ~B
656+
let negated_intersection = IntersectionBuilder::new(&db)
657+
.add_negative(Type::Intersection(intersection))
646658
.build()
647659
.expect_union();
648660

649-
// should have as elements: (~Sized),(~Hashable)
650-
let not_st = st.negate(&db);
651-
let not_ht = ht.negate(&db);
652-
assert_eq!(not_s_h_t.elements(&db), &[not_st, not_ht]);
661+
// should have as elements ~A and ~B
662+
let not_a = a.negate(&db);
663+
let not_b = b.negate(&db);
664+
assert_eq!(negated_intersection.elements(&db), &[not_a, not_b]);
653665
}
654666

655667
#[test]
656668
fn mixed_intersection_negation_distributes_over_union() {
657-
let db = setup_db();
658-
let it = KnownClass::Int.to_instance(&db);
659-
let st = typing_symbol(&db, "Sized").expect_type().to_instance(&db);
660-
let ht = typing_symbol(&db, "Hashable")
669+
let mut db = setup_db();
670+
db.write_dedented(
671+
"/src/module.py",
672+
r#"
673+
class A: ...
674+
class B: ...
675+
"#,
676+
)
677+
.unwrap();
678+
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();
679+
680+
let a = global_symbol(&db, module, "A")
681+
.expect_type()
682+
.to_instance(&db);
683+
let b = global_symbol(&db, module, "B")
661684
.expect_type()
662685
.to_instance(&db);
663-
// s_not_h_t: Sized & ~Hashable
664-
let s_not_h_t = IntersectionBuilder::new(&db)
665-
.add_positive(st)
666-
.add_negative(ht)
686+
let int = KnownClass::Int.to_instance(&db);
687+
688+
// a_not_b: A & ~B
689+
let a_not_b = IntersectionBuilder::new(&db)
690+
.add_positive(a)
691+
.add_negative(b)
667692
.build()
668693
.expect_intersection();
669-
assert_eq!(s_not_h_t.pos_vec(&db), &[st]);
670-
assert_eq!(s_not_h_t.neg_vec(&db), &[ht]);
671-
672-
// let's build int & ~(Sized & ~Hashable)
673-
let tt = IntersectionBuilder::new(&db)
674-
.add_positive(it)
675-
.add_negative(Type::Intersection(s_not_h_t))
694+
assert_eq!(a_not_b.pos_vec(&db), &[a]);
695+
assert_eq!(a_not_b.neg_vec(&db), &[b]);
696+
697+
// let's build
698+
// int & ~(A & ~B)
699+
// = int & ~(A & ~B)
700+
// = int & (~A | B)
701+
// = (int & ~A) | (int & B)
702+
let t = IntersectionBuilder::new(&db)
703+
.add_positive(int)
704+
.add_negative(Type::Intersection(a_not_b))
676705
.build();
677-
678-
// int & ~(Sized & ~Hashable)
679-
// -> int & (~Sized | Hashable)
680-
// -> (int & ~Sized) | (int & Hashable)
681-
assert_eq!(tt.display(&db).to_string(), "int & ~Sized | int & Hashable");
706+
assert_eq!(t.display(&db).to_string(), "int & ~A | int & B");
682707
}
683708

684709
#[test]
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
from airflow.operators import PythonOperator
2+
from airflow.providers.airbyte.operators.airbyte import AirbyteTriggerSyncOperator
3+
from airflow.providers.amazon.aws.operators.appflow import AppflowFlowRunOperator
24

35

46
def my_callable():
57
pass
68

79

810
my_task = PythonOperator(task_id="my_task", callable=my_callable)
9-
my_task_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
11+
incorrect_name = PythonOperator(task_id="my_task") # AIR001
1012

11-
incorrect_name = PythonOperator(task_id="my_task")
12-
incorrect_name_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
13+
my_task = AirbyteTriggerSyncOperator(task_id="my_task", callable=my_callable)
14+
incorrect_name = AirbyteTriggerSyncOperator(task_id="my_task") # AIR001
1315

14-
from my_module import MyClass
16+
my_task = AppflowFlowRunOperator(task_id="my_task", callable=my_callable)
17+
incorrect_name = AppflowFlowRunOperator(task_id="my_task") # AIR001
1518

16-
incorrect_name = MyClass(task_id="my_task")
19+
# Consider only from the `airflow.operators` (or providers operators) module
20+
from airflow import MyOperator
21+
22+
incorrect_name = MyOperator(task_id="my_task")
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,52 @@
1+
from airflow.triggers.external_task import TaskStateTrigger
2+
from airflow.www.auth import has_access
3+
from airflow.api_connexion.security import requires_access
4+
from airflow.metrics.validators import AllowListValidator
5+
from airflow.metrics.validators import BlockListValidator
16
from airflow.utils import dates
2-
from airflow.utils.dates import date_range, datetime_to_nano, days_ago
7+
from airflow.utils.dates import (
8+
date_range,
9+
datetime_to_nano,
10+
days_ago,
11+
infer_time_unit,
12+
parse_execution_date,
13+
round_time,
14+
scale_time_units,
15+
)
16+
from airflow.utils.file import TemporaryDirectory, mkdirs
17+
from airflow.utils.state import SHUTDOWN, terminating_states
18+
from airflow.utils.dag_cycle_tester import test_cycle
19+
20+
21+
TaskStateTrigger
322

4-
date_range
5-
days_ago
23+
24+
has_access
25+
requires_access
26+
27+
AllowListValidator
28+
BlockListValidator
629

730
dates.date_range
831
dates.days_ago
932

33+
date_range
34+
days_ago
35+
parse_execution_date
36+
round_time
37+
scale_time_units
38+
infer_time_unit
39+
40+
1041
# This one was not deprecated.
1142
datetime_to_nano
1243
dates.datetime_to_nano
44+
45+
TemporaryDirectory
46+
mkdirs
47+
48+
SHUTDOWN
49+
terminating_states
50+
51+
52+
test_cycle

crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py

+9
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,12 @@
5757
f"{{}}+-\d"
5858
f"\n{{}}+-\d+"
5959
f"\n{{}}�+-\d+"
60+
61+
# See https://github.com/astral-sh/ruff/issues/11491
62+
total = 10
63+
ok = 7
64+
incomplete = 3
65+
s = f"TOTAL: {total}\nOK: {ok}\INCOMPLETE: {incomplete}\n"
66+
67+
# Debug text (should trigger)
68+
t = f"{'\InHere'=}"

crates/ruff_linter/src/checkers/ast/analyze/statement.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -1554,11 +1554,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
15541554
.rules
15551555
.enabled(Rule::AirflowVariableNameTaskIdMismatch)
15561556
{
1557-
if let Some(diagnostic) =
1558-
airflow::rules::variable_name_task_id(checker, targets, value)
1559-
{
1560-
checker.diagnostics.push(diagnostic);
1561-
}
1557+
airflow::rules::variable_name_task_id(checker, targets, value);
15621558
}
15631559
if checker.settings.rules.enabled(Rule::SelfAssigningVariable) {
15641560
pylint::rules::self_assignment(checker, assign);

crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs

+65-3
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl Violation for Airflow3Removal {
5151
match replacement {
5252
Replacement::None => format!("`{deprecated}` is removed in Airflow 3.0"),
5353
Replacement::Name(name) => {
54-
format!("`{deprecated}` is removed in Airflow 3.0; use {name} instead")
54+
format!("`{deprecated}` is removed in Airflow 3.0; use `{name}` instead")
5555
}
5656
}
5757
}
@@ -103,13 +103,75 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
103103
.semantic()
104104
.resolve_qualified_name(expr)
105105
.and_then(|qualname| match qualname.segments() {
106-
["airflow", "utils", "dates", "date_range"] => {
106+
["airflow", "triggers", "external_task", "TaskStateTrigger"] => {
107107
Some((qualname.to_string(), Replacement::None))
108108
}
109+
["airflow", "www", "auth", "has_access"] => Some((
110+
qualname.to_string(),
111+
Replacement::Name("airflow.www.auth.has_access_*".to_string()),
112+
)),
113+
["airflow", "api_connexion", "security", "requires_access"] => Some((
114+
qualname.to_string(),
115+
Replacement::Name(
116+
"airflow.api_connexion.security.requires_access_*".to_string(),
117+
),
118+
)),
119+
// airflow.metrics.validators
120+
["airflow", "metrics", "validators", "AllowListValidator"] => Some((
121+
qualname.to_string(),
122+
Replacement::Name(
123+
"airflow.metrics.validators.PatternAllowListValidator".to_string(),
124+
),
125+
)),
126+
["airflow", "metrics", "validators", "BlockListValidator"] => Some((
127+
qualname.to_string(),
128+
Replacement::Name(
129+
"airflow.metrics.validators.PatternBlockListValidator".to_string(),
130+
),
131+
)),
132+
// airflow.utils.dates
133+
["airflow", "utils", "dates", "date_range"] => Some((
134+
qualname.to_string(),
135+
Replacement::Name("airflow.timetables.".to_string()),
136+
)),
109137
["airflow", "utils", "dates", "days_ago"] => Some((
110138
qualname.to_string(),
111-
Replacement::Name("datetime.timedelta()".to_string()),
139+
Replacement::Name("pendulum.today('UTC').add(days=-N, ...)".to_string()),
140+
)),
141+
["airflow", "utils", "dates", "parse_execution_date"] => {
142+
Some((qualname.to_string(), Replacement::None))
143+
}
144+
["airflow", "utils", "dates", "round_time"] => {
145+
Some((qualname.to_string(), Replacement::None))
146+
}
147+
["airflow", "utils", "dates", "scale_time_units"] => {
148+
Some((qualname.to_string(), Replacement::None))
149+
}
150+
["airflow", "utils", "dates", "infer_time_unit"] => {
151+
Some((qualname.to_string(), Replacement::None))
152+
}
153+
// airflow.utils.file
154+
["airflow", "utils", "file", "TemporaryDirectory"] => {
155+
Some((qualname.to_string(), Replacement::None))
156+
}
157+
["airflow", "utils", "file", "mkdirs"] => Some((
158+
qualname.to_string(),
159+
Replacement::Name("pendulum.today('UTC').add(days=-N, ...)".to_string()),
112160
)),
161+
// airflow.utils.state
162+
["airflow", "utils", "state", "SHUTDOWN"] => {
163+
Some((qualname.to_string(), Replacement::None))
164+
}
165+
["airflow", "utils", "state", "terminating_states"] => {
166+
Some((qualname.to_string(), Replacement::None))
167+
}
168+
// airflow.uilts
169+
["airflow", "utils", "dag_cycle_tester", "test_cycle"] => {
170+
Some((qualname.to_string(), Replacement::None))
171+
}
172+
["airflow", "utils", "decorators", "apply_defaults"] => {
173+
Some((qualname.to_string(), Replacement::None))
174+
}
113175
_ => None,
114176
});
115177
if let Some((deprecated, replacement)) = result {

0 commit comments

Comments
 (0)