From 6596a92e2818509812ee0dc71f6beabede7397d4 Mon Sep 17 00:00:00 2001 From: "Rebecca Chen (Python)" Date: Fri, 22 Nov 2024 10:22:43 -0800 Subject: [PATCH] Support forward references in type aliases Summary: Based on the conformance tests, forward references should be supported in scoped and explicit type aliases, but not in implicit type aliases. For explicit type aliases, we unfortunately have to match on the "TypeAlias" name. Reviewed By: samwgoldman, ndmitchell Differential Revision: D66318809 fbshipit-source-id: ef8419e63006803f02f708e60608e6d5132366e1 --- pyre2/conformance/third_party/conformance.exp | 70 ++++++++----------- .../third_party/conformance.result | 2 + pyre2/conformance/third_party/results.json | 4 +- pyre2/pyre2/bin/alt/bindings.rs | 17 +++-- pyre2/pyre2/bin/test/type_alias.rs | 21 ++++++ 5 files changed, 67 insertions(+), 47 deletions(-) diff --git a/pyre2/conformance/third_party/conformance.exp b/pyre2/conformance/third_party/conformance.exp index 584aae8568..4500f46d10 100644 --- a/pyre2/conformance/third_party/conformance.exp +++ b/pyre2/conformance/third_party/conformance.exp @@ -966,36 +966,6 @@ "name": "PyreError", "stop_column": 32, "stop_line": 69 - }, - { - "code": -2, - "column": 35, - "concise_description": "untype, got Literal['RecursiveUnion']", - "description": "untype, got Literal['RecursiveUnion']", - "line": 72, - "name": "PyreError", - "stop_column": 51, - "stop_line": 72 - }, - { - "code": -2, - "column": 37, - "concise_description": "untype, got Literal['MutualReference2']", - "description": "untype, got Literal['MutualReference2']", - "line": 75, - "name": "PyreError", - "stop_column": 55, - "stop_line": 75 - }, - { - "code": -2, - "column": 99, - "concise_description": "untype, got Literal['MutualReference1']", - "description": "untype, got Literal['MutualReference1']", - "line": 75, - "name": "PyreError", - "stop_column": 117, - "stop_line": 75 } ], "aliases_type_statement.py": [ @@ -1089,6 +1059,16 @@ "stop_column": 30, "stop_line": 23 }, + { + "code": -2, + "column": 27, + "concise_description": "Could not parse type string: , got Expected an expression at byte range 791..791", + "description": "Could not parse type string: , got Expected an expression at byte range 791..791", + "line": 37, + "name": "PyreError", + "stop_column": 29, + "stop_line": 37 + }, { "code": -2, "column": 27, @@ -1171,22 +1151,32 @@ }, { "code": -2, - "column": 42, - "concise_description": "Expected 0 positional argument(s)", - "description": "Expected 0 positional argument(s)", - "line": 40, + "column": 1, + "concise_description": "Expected `BadTypeAlias5` to be a type alias, got dict[Error, Error]", + "description": "Expected `BadTypeAlias5` to be a type alias, got dict[Error, Error]", + "line": 41, "name": "PyreError", - "stop_column": 43, - "stop_line": 40 + "stop_column": 32, + "stop_line": 41 }, { "code": -2, - "column": 1, - "concise_description": "Expected `BadTypeAlias5` to be a type alias, got dict[str, str]", - "description": "Expected `BadTypeAlias5` to be a type alias, got dict[str, str]", + "column": 23, + "concise_description": "Could not find name `a`", + "description": "Could not find name `a`", "line": 41, "name": "PyreError", - "stop_column": 32, + "stop_column": 24, + "stop_line": 41 + }, + { + "code": -2, + "column": 28, + "concise_description": "Could not find name `b`", + "description": "Could not find name `b`", + "line": 41, + "name": "PyreError", + "stop_column": 29, "stop_line": 41 }, { diff --git a/pyre2/conformance/third_party/conformance.result b/pyre2/conformance/third_party/conformance.result index 3fe24ef72a..65dc9efd11 100644 --- a/pyre2/conformance/third_party/conformance.result +++ b/pyre2/conformance/third_party/conformance.result @@ -68,6 +68,8 @@ "Line 50: Expected 1 errors", "Line 51: Expected 1 errors", "Line 52: Expected 1 errors", + "Line 72: Expected 1 errors", + "Line 75: Expected 1 errors", "Line 14: Unexpected errors [\"untype, got Literal['Json']\", \"untype, got Literal['Json']\"]", "Line 24: Unexpected errors [\"untype, got Literal['Json2']\", \"untype, got Literal['Json2']\"]", "Line 30: Unexpected errors [\"untype, got Literal['RecursiveTuple']\"]", diff --git a/pyre2/conformance/third_party/results.json b/pyre2/conformance/third_party/results.json index 775159e67f..0f91c80fc4 100644 --- a/pyre2/conformance/third_party/results.json +++ b/pyre2/conformance/third_party/results.json @@ -3,7 +3,7 @@ "pass": 7, "fail": 126, "pass_rate": 0.05, - "differences": 1489, + "differences": 1491, "passing": [ "directives_no_type_check.py", "directives_type_ignore.py", @@ -17,7 +17,7 @@ "aliases_explicit.py": 19, "aliases_implicit.py": 25, "aliases_newtype.py": 10, - "aliases_recursive.py": 17, + "aliases_recursive.py": 19, "aliases_type_statement.py": 17, "aliases_typealiastype.py": 24, "aliases_variance.py": 3, diff --git a/pyre2/pyre2/bin/alt/bindings.rs b/pyre2/pyre2/bin/alt/bindings.rs index 8a6b207ece..1bb261d508 100644 --- a/pyre2/pyre2/bin/alt/bindings.rs +++ b/pyre2/pyre2/bin/alt/bindings.rs @@ -1154,15 +1154,22 @@ impl<'a> BindingsBuilder<'a> { let ann_val = if let Some(special) = SpecialForm::new(&name.id, &x.annotation) { BindingAnnotation::Type(special.to_type()) } else { - BindingAnnotation::AnnotateExpr(*x.annotation, None) + BindingAnnotation::AnnotateExpr(*x.annotation.clone(), None) }; let ann_key = self.table.insert(ann_key, ann_val); - if let Some(value) = x.value + if let Some(mut value) = x.value && (!self.module_info.is_interface() || !matches!(&*value, Expr::EllipsisLiteral(_))) { - self.ensure_expr(&value); + // Handle forward references in explicit type aliases. + if let Expr::Name(name) = *x.annotation + && name.id == "TypeAlias" + { + self.ensure_type(&mut value, &mut BindingsBuilder::forward_lookup); + } else { + self.ensure_expr(&value); + } let range = value.range(); self.bind_definition( &name.clone(), @@ -1220,14 +1227,14 @@ impl<'a> BindingsBuilder<'a> { } _ => self.todo("Bindings::stmt AnnAssign", &x), }, - Stmt::TypeAlias(x) => { + Stmt::TypeAlias(mut x) => { if let Expr::Name(name) = *x.name { let qs = if let Some(params) = x.type_params { self.type_params(¶ms) } else { Vec::new() }; - self.ensure_expr(&x.value); + self.ensure_type(&mut x.value, &mut BindingsBuilder::forward_lookup); let expr_binding = Binding::Expr(None, *x.value); let binding = Binding::ScopedTypeAlias( name.id.clone(), diff --git a/pyre2/pyre2/bin/test/type_alias.rs b/pyre2/pyre2/bin/test/type_alias.rs index 8f362fc949..3795f1b0f0 100644 --- a/pyre2/pyre2/bin/test/type_alias.rs +++ b/pyre2/pyre2/bin/test/type_alias.rs @@ -129,3 +129,24 @@ X2.__add__ # ok X3.__add__ # E: Cannot use type alias `X3` "#, ); + +simple_test!( + test_forward_ref, + r#" +from typing import TypeAlias, assert_type + +X1 = "A" # Just a normal alias to a str +X2: TypeAlias = "A" # Forward ref +type X3 = "A" # Forward ref + +class A: + pass + +def f(x1: X1): # E: untype, got Literal['A'] + pass + +def g(x2: X2, x3: X3): + assert_type(x2, A) + assert_type(x3, A) + "#, +);