Skip to content

Commit

Permalink
Support forward references in TypeVar declarations
Browse files Browse the repository at this point in the history
Summary: Since forward references are handled in bindings.rs, the best we can do is look for TypeVar declarations by name. This won't work if a user renames TypeVar, but I imagine that's pretty rare.

Reviewed By: ndmitchell

Differential Revision: D66280593

fbshipit-source-id: 60f9c9e800efe2a09689552f057f3538212c22a5
  • Loading branch information
rchen152 authored and facebook-github-bot committed Nov 21, 2024
1 parent 3e3f32e commit a1ed9ad
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 97 deletions.
80 changes: 0 additions & 80 deletions pyre2/conformance/third_party/conformance.exp
Original file line number Diff line number Diff line change
Expand Up @@ -2471,16 +2471,6 @@
}
],
"annotations_methods.py": [
{
"code": -2,
"column": 24,
"concise_description": "untype, got Literal['A']",
"description": "untype, got Literal['A']",
"line": 10,
"name": "PyreError",
"stop_column": 27,
"stop_line": 10
},
{
"code": -2,
"column": 16,
Expand Down Expand Up @@ -11769,16 +11759,6 @@
"stop_column": 24,
"stop_line": 63
},
{
"code": -2,
"column": 32,
"concise_description": "untype, got Literal['Foo2']",
"description": "untype, got Literal['Foo2']",
"line": 78,
"name": "PyreError",
"stop_column": 38,
"stop_line": 78
},
{
"code": -2,
"column": 9,
Expand Down Expand Up @@ -14915,16 +14895,6 @@
}
],
"generics_upper_bound.py": [
{
"code": -2,
"column": 36,
"concise_description": "untype, got Literal['ForwardRef | str']",
"description": "untype, got Literal['ForwardRef | str']",
"line": 12,
"name": "PyreError",
"stop_column": 54,
"stop_line": 12
},
{
"code": -2,
"column": 1,
Expand Down Expand Up @@ -14974,16 +14944,6 @@
"name": "PyreError",
"stop_column": 50,
"stop_line": 56
},
{
"code": -2,
"column": 44,
"concise_description": "untype, got Literal['int']",
"description": "untype, got Literal['int']",
"line": 56,
"name": "PyreError",
"stop_column": 49,
"stop_line": 56
}
],
"generics_variance.py": [
Expand Down Expand Up @@ -17278,16 +17238,6 @@
"stop_column": 35,
"stop_line": 32
},
{
"code": -2,
"column": 28,
"concise_description": "untype, got Literal['A']",
"description": "untype, got Literal['A']",
"line": 37,
"name": "PyreError",
"stop_column": 31,
"stop_line": 37
},
{
"code": -2,
"column": 16,
Expand Down Expand Up @@ -17660,16 +17610,6 @@
"stop_column": 30,
"stop_line": 38
},
{
"code": -2,
"column": 28,
"concise_description": "untype, got Literal['A']",
"description": "untype, got Literal['A']",
"line": 41,
"name": "PyreError",
"stop_column": 31,
"stop_line": 41
},
{
"code": -2,
"column": 16,
Expand Down Expand Up @@ -19259,26 +19199,6 @@
],
"protocols_runtime_checkable.py": [],
"protocols_self.py": [
{
"code": -2,
"column": 24,
"concise_description": "untype, got Literal['Copyable']",
"description": "untype, got Literal['Copyable']",
"line": 10,
"name": "PyreError",
"stop_column": 34,
"stop_line": 10
},
{
"code": -2,
"column": 24,
"concise_description": "untype, got Literal['Other']",
"description": "untype, got Literal['Other']",
"line": 23,
"name": "PyreError",
"stop_column": 31,
"stop_line": 23
},
{
"code": -2,
"column": 5,
Expand Down
7 changes: 0 additions & 7 deletions pyre2/conformance/third_party/conformance.result
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@
"Line 187: Unexpected errors ['EXPECTED None <: AsyncIterator[int]', 'TODO: ExprYield - Answers::expr_infer']"
],
"annotations_methods.py": [
"Line 10: Unexpected errors [\"untype, got Literal['A']\"]",
"Line 19: Unexpected errors ['Expected a callable, got type[?_]']",
"Line 23: Unexpected errors ['Expected a callable, got type[?_]']",
"Line 30: Unexpected errors ['assert_type(Any, A) failed']",
Expand Down Expand Up @@ -880,7 +879,6 @@
"Line 59: Unexpected errors [\"Missing argument 'flush'\"]",
"Line 60: Unexpected errors [\"Missing argument 'flush'\", 'TODO: Answers::expr_infer attribute: `Self`.x']",
"Line 63: Unexpected errors ['EXPECTED HasNestedFunction <: Self']",
"Line 78: Unexpected errors [\"untype, got Literal['Foo2']\"]",
"Line 93: Unexpected errors ['Object of class `Foo3` has no attribute `child_value`']"
],
"generics_syntax_compatibility.py": [],
Expand Down Expand Up @@ -1071,7 +1069,6 @@
"generics_upper_bound.py": [
"Line 24: Expected 1 errors",
"Line 51: Expected 1 errors",
"Line 12: Unexpected errors [\"untype, got Literal['ForwardRef | str']\"]",
"Line 37: Unexpected errors ['assert_type(list[Any], list[int]) failed']",
"Line 38: Unexpected errors ['assert_type(set[Any], set[int]) failed']"
],
Expand Down Expand Up @@ -1224,7 +1221,6 @@
"Line 25: Unexpected errors ['EXPECTED bool <: TypeGuard[list[str]]', 'EXPECTED Generator[bool, None, None] <: Iterable[object]', 'EXPECTED type[str] <: UnionType | type | tuple[Unknown, ...]', 'Expected `list.__iter__` to be a callable, got Callable[[_PyreReadOnly_[list[object]]], Iterator[_PyreReadOnly_[object]]] | Callable[[list[object]], Iterator[object]]']",
"Line 28: Unexpected errors ['EXPECTED bool <: TypeGuard[set[?_]]', 'EXPECTED Generator[bool, None, None] <: Iterable[object]', 'EXPECTED type[?_] <: UnionType | type | tuple[Unknown, ...]']",
"Line 32: Unexpected errors ['assert_type(set[object], set[int]) failed']",
"Line 37: Unexpected errors [\"untype, got Literal['A']\"]",
"Line 41: Unexpected errors ['EXPECTED bool <: TypeGuard[int]', 'EXPECTED type[int] <: UnionType | type | tuple[Unknown, ...]']",
"Line 45: Unexpected errors ['EXPECTED bool <: TypeGuard[int]', 'EXPECTED type[int] <: UnionType | type | tuple[Unknown, ...]']",
"Line 49: Unexpected errors ['EXPECTED bool <: TypeGuard[int]', 'EXPECTED type[int] <: UnionType | type | tuple[Unknown, ...]']",
Expand Down Expand Up @@ -1256,7 +1252,6 @@
"Line 28: Unexpected errors ['EXPECTED bool <: TypeIs[Awaitable[Any]]', 'EXPECTED type[Awaitable] <: UnionType | type | tuple[Unknown, ...]']",
"Line 35: Unexpected errors ['TODO: Await(ExprAwait - Answers::expr_infer']",
"Line 38: Unexpected errors ['assert_type(Awaitable[int] | int, int) failed']",
"Line 41: Unexpected errors [\"untype, got Literal['A']\"]",
"Line 45: Unexpected errors ['EXPECTED bool <: TypeIs[int]', 'EXPECTED type[int] <: UnionType | type | tuple[Unknown, ...]']",
"Line 49: Unexpected errors ['EXPECTED bool <: TypeIs[int]', 'EXPECTED type[int] <: UnionType | type | tuple[Unknown, ...]']",
"Line 53: Unexpected errors ['EXPECTED bool <: TypeIs[int]', 'EXPECTED type[int] <: UnionType | type | tuple[Unknown, ...]']",
Expand Down Expand Up @@ -1387,8 +1382,6 @@
"Line 96: Expected 1 errors"
],
"protocols_self.py": [
"Line 10: Unexpected errors [\"untype, got Literal['Copyable']\"]",
"Line 23: Unexpected errors [\"untype, got Literal['Other']\"]",
"Line 32: Unexpected errors ['EXPECTED One <: Copyable']",
"Line 33: Unexpected errors ['EXPECTED Other <: Copyable']",
"Line 54: Unexpected errors ['EXPECTED C1[str] <: P1Parent[str]']",
Expand Down
14 changes: 7 additions & 7 deletions pyre2/conformance/third_party/results.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"pass": 7,
"fail": 126,
"pass_rate": 0.05,
"differences": 1496,
"differences": 1489,
"passing": [
"directives_no_type_check.py",
"directives_type_ignore.py",
Expand All @@ -24,7 +24,7 @@
"annotations_coroutines.py": 3,
"annotations_forward_refs.py": 9,
"annotations_generators.py": 32,
"annotations_methods.py": 11,
"annotations_methods.py": 10,
"annotations_typeexpr.py": 5,
"callables_annotation.py": 26,
"callables_kwargs.py": 18,
Expand Down Expand Up @@ -79,7 +79,7 @@
"generics_self_attributes.py": 2,
"generics_self_basic.py": 11,
"generics_self_protocols.py": 2,
"generics_self_usage.py": 19,
"generics_self_usage.py": 18,
"generics_syntax_declarations.py": 14,
"generics_syntax_infer_variance.py": 7,
"generics_syntax_scoping.py": 18,
Expand All @@ -91,7 +91,7 @@
"generics_typevartuple_overloads.py": 6,
"generics_typevartuple_specialization.py": 49,
"generics_typevartuple_unpack.py": 7,
"generics_upper_bound.py": 5,
"generics_upper_bound.py": 4,
"generics_variance.py": 13,
"generics_variance_inference.py": 10,
"historical_positional.py": 4,
Expand All @@ -103,8 +103,8 @@
"namedtuples_define_functional.py": 18,
"namedtuples_type_compat.py": 4,
"namedtuples_usage.py": 13,
"narrowing_typeguard.py": 29,
"narrowing_typeis.py": 33,
"narrowing_typeguard.py": 28,
"narrowing_typeis.py": 32,
"overloads_basic.py": 4,
"protocols_class_objects.py": 7,
"protocols_definition.py": 39,
Expand All @@ -114,7 +114,7 @@
"protocols_modules.py": 4,
"protocols_recursive.py": 4,
"protocols_runtime_checkable.py": 6,
"protocols_self.py": 8,
"protocols_self.py": 6,
"protocols_subtyping.py": 10,
"protocols_variance.py": 7,
"qualifiers_annotated.py": 13,
Expand Down
35 changes: 32 additions & 3 deletions pyre2/pyre2/bin/alt/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use ruff_python_ast::name::Name;
use ruff_python_ast::Comprehension;
use ruff_python_ast::Expr;
use ruff_python_ast::ExprAttribute;
use ruff_python_ast::ExprCall;
use ruff_python_ast::ExprName;
use ruff_python_ast::ExprNoneLiteral;
use ruff_python_ast::ExprSubscript;
Expand Down Expand Up @@ -1088,12 +1089,40 @@ impl<'a> BindingsBuilder<'a> {
} else {
None
};
self.ensure_expr(&x.value);
let mut value = *x.value;
// Handle forward references in a TypeVar call.
match &mut value {
Expr::Call(ExprCall {
range: _,
func: box Expr::Name(name),
arguments,
}) if name.id == "TypeVar" && !arguments.is_empty() => {
self.ensure_expr(&Expr::Name(name.clone()));
// The constraints (i.e., any positional arguments after the first)
// and the "bound" keyword argument are types.
for arg in arguments.args[1..].iter_mut() {
self.ensure_type(arg, &mut BindingsBuilder::forward_lookup);
}
for kw in arguments.keywords.iter_mut() {
if let Some(id) = &kw.arg
&& id.id == "bound"
{
self.ensure_type(
&mut kw.value,
&mut BindingsBuilder::forward_lookup,
);
} else {
self.ensure_expr(&kw.value);
}
}
}
_ => self.ensure_expr(&value),
}
for target in x.targets.iter() {
let make_binding = |k: Option<Idx<KeyAnnotation>>| {
let b = Binding::Expr(k, *x.value.clone());
let b = Binding::Expr(k, value.clone());
if let Some(name) = &name {
Binding::NameAssign(name.clone(), k, Box::new(b), x.value.range())
Binding::NameAssign(name.clone(), k, Box::new(b), value.range())
} else {
b
}
Expand Down
25 changes: 25 additions & 0 deletions pyre2/pyre2/bin/test/legacy_generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,28 @@ T2 = TypeVar('T2', covariant=True, contravariant=False)
T3 = TypeVar('T3', covariant="lunch") # E: Expected literal True or False
"#,
);

simple_test!(
test_tvar_forward_ref,
r#"
from typing import TypeVar
T1 = TypeVar('T1', bound='A')
T2 = TypeVar('T2', bound='B') # E: Could not find name `B`
T3 = TypeVar('T3', 'A', int)
T4 = TypeVar('T4', 'B', int) # E: Could not find name `B`
class A:
pass
"#,
);

simple_test!(
test_tvar_class_constraint,
r#"
from typing import TypeVar
class A:
pass
T1 = TypeVar('T1', int, A)
T2 = TypeVar('T2', int, B) # E: Could not find name `B`
"#,
);

0 comments on commit a1ed9ad

Please sign in to comment.