Skip to content

Commit 6fa0873

Browse files
[ty] Respect @no_type_check when combined with other decorators (#23177)
## Summary Closes astral-sh/ty#141.
1 parent 459c417 commit 6fa0873

2 files changed

Lines changed: 15 additions & 14 deletions

File tree

crates/ty_python_semantic/resources/mdtest/suppressions/no_type_check.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ from typing import no_type_check
4848
@unknown_decorator # error: [unresolved-reference]
4949
@no_type_check
5050
def test() -> int:
51-
# TODO: this should not be an error
52-
# error: [unresolved-reference]
5351
return a + 5
5452
```
5553

@@ -66,8 +64,6 @@ from typing import no_type_check
6664
@no_type_check
6765
@unknown_decorator
6866
def test() -> int:
69-
# TODO: this should not be an error
70-
# error: [unresolved-reference]
7167
return a + 5
7268
```
7369

crates/ty_python_semantic/src/types/context.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use ruff_db::{
99
};
1010
use ruff_text_size::{Ranged, TextRange};
1111

12-
use super::{Type, TypeCheckDiagnostics, binding_type};
12+
use super::{Type, TypeCheckDiagnostics, infer_definition_types};
1313

1414
use crate::diagnostic::DiagnosticGuard;
1515
use crate::lint::LintSource;
@@ -190,17 +190,22 @@ impl<'db, 'ast> InferContext<'db, 'ast> {
190190

191191
let scope_id = self.scope.file_scope_id(self.db);
192192

193-
// Inspect all ancestor function scopes by walking bottom up and infer the function's type.
194-
let mut function_scope_tys = index
193+
// Inspect all ancestor function scopes by walking bottom up and check
194+
// if any is decorated with `@no_type_check`. We use the undecorated type
195+
// rather than the binding type because other decorators (e.g. unknown ones)
196+
// may transform the function type into a non-`FunctionLiteral`.
197+
// `undecorated_type()` can be `None` during cycle recovery.
198+
index
195199
.ancestor_scopes(scope_id)
196200
.filter_map(|(_, scope)| scope.node().as_function())
197-
.map(|node| binding_type(self.db, index.expect_single_definition(node)))
198-
.filter_map(Type::as_function_literal);
199-
200-
// Iterate over all functions and test if any is decorated with `@no_type_check`.
201-
function_scope_tys.any(|function_ty| {
202-
function_ty.has_known_decorator(self.db, FunctionDecorators::NO_TYPE_CHECK)
203-
})
201+
.filter_map(|node| {
202+
infer_definition_types(self.db, index.expect_single_definition(node))
203+
.undecorated_type()
204+
.and_then(Type::as_function_literal)
205+
})
206+
.any(|function_ty| {
207+
function_ty.has_known_decorator(self.db, FunctionDecorators::NO_TYPE_CHECK)
208+
})
204209
}
205210
InNoTypeCheck::Yes => true,
206211
}

0 commit comments

Comments
 (0)