Skip to content

Commit

Permalink
Add None type special type
Browse files Browse the repository at this point in the history
Signed-off-by: Shaygan <[email protected]>
  • Loading branch information
Glyphack committed Aug 29, 2024
1 parent 0ed9f63 commit 3b0e36d
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 4 deletions.
4 changes: 4 additions & 0 deletions typechecker/src/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -688,4 +688,8 @@ mod tests {
annotations_coroutine,
"test_data/inputs/conformance_tests/annotations_coroutine.py"
);
type_eval_test!(
specialtypes_none,
"test_data/inputs/conformance_tests/specialtypes_none.py"
);
}
60 changes: 56 additions & 4 deletions typechecker/src/type_evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ impl<'a> TypeEvaluator<'a> {
c.details.clone(),
vec![final_elm_type],
true,
vec![],
)))
}
ast::Expression::Tuple(t) => {
Expand All @@ -181,6 +182,7 @@ impl<'a> TypeEvaluator<'a> {
c.details.clone(),
vec![elm_type],
true,
vec![],
)))
}
ast::Expression::Dict(d) => {
Expand All @@ -196,6 +198,7 @@ impl<'a> TypeEvaluator<'a> {
c.details.clone(),
vec![key_type, value_type],
true,
vec![],
)))
}
ast::Expression::Set(s) => {
Expand All @@ -211,6 +214,7 @@ impl<'a> TypeEvaluator<'a> {
class_type,
vec![elm_type],
true,
vec![],
)))
}
ast::Expression::BoolOp(_) => Ok(self.get_builtin_type("bool").expect("typeshed")),
Expand Down Expand Up @@ -312,16 +316,43 @@ impl<'a> TypeEvaluator<'a> {
}
};
match value_type {
PythonType::Class(c) => Ok(self
.lookup_on_class(symbol_table, &c, &a.attr)
.expect("attribute not found on type")),
PythonType::Class(ref c) => {
let attribute_on_c = self.lookup_on_class(symbol_table, &c, &a.attr);
if let Some(attribute_on_c) = attribute_on_c {
Ok(attribute_on_c)
} else {
let bases = self.get_base_classes(value_type);
for base in bases {
let base_class = base.expect_class();
let attribute_on_base =
self.lookup_on_class(symbol_table, &base_class, &a.attr);
if let Some(attribute_on_base) = attribute_on_base {
return Ok(attribute_on_base);
}
}

Ok(PythonType::Unknown)
}
}
PythonType::Module(module) => {
log::debug!("module: {:?}", module);
let module_sym_table =
self.imported_symbol_tables.get(&module.module_id).unwrap();
self.get_name_type(&a.attr, None, &module_sym_table, Some(0))
}
_ => Ok(PythonType::Unknown),
_ => {
let bases = self.get_base_classes(value_type);
for base in bases {
let base_class = base.expect_class();
let attribute_on_base =
self.lookup_on_class(symbol_table, &base_class, &a.attr);
if let Some(attribute_on_base) = attribute_on_base {
return Ok(attribute_on_base);
}
}

Ok(PythonType::Unknown)
}
}
}
ast::Expression::BinOp(b) => Ok(self.bin_op_result_type(
Expand Down Expand Up @@ -447,6 +478,7 @@ impl<'a> TypeEvaluator<'a> {
class_type.details.clone(),
type_parameters,
true,
vec![],
))
}
// Illegal type annotation? Trying to subscript a non class type
Expand Down Expand Up @@ -547,6 +579,7 @@ impl<'a> TypeEvaluator<'a> {
class_symbol,
vec![],
false,
vec![],
)))
} else {
Ok(var_type)
Expand Down Expand Up @@ -739,11 +772,14 @@ impl<'a> TypeEvaluator<'a> {
};

let mut class_def_type_parameters = vec![];
let mut base_classes = vec![];
for base in bases {
let base_type = self.get_type(base, Some(symbol_table), None);
let Ok(PythonType::Class(c)) = base_type else {
continue;
};
// TODO: Maybe unnecessary clone
base_classes.push(c.clone());
let Some(possible_type_parameter) = base.as_subscript() else {
class_def_type_parameters.extend(c.type_parameters);
continue;
Expand Down Expand Up @@ -794,6 +830,7 @@ impl<'a> TypeEvaluator<'a> {
class_symbol.clone(),
class_def_type_parameters,
false,
base_classes,
)))
}
}
Expand Down Expand Up @@ -1168,4 +1205,19 @@ impl<'a> TypeEvaluator<'a> {
true,
)))
}

fn get_base_classes(&self, python_type: PythonType) -> Vec<PythonType> {
let mut super_classes = vec![];
if let PythonType::Class(c) = python_type {
super_classes.push(PythonType::Class(c.clone()));
for super_class in &c.base_classes {
super_classes.push(PythonType::Class(super_class.clone()));
}
}
let object_class = self.get_builtin_type("object").expect("object not found");

super_classes.push(object_class.clone());

super_classes
}
}
7 changes: 7 additions & 0 deletions typechecker/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ pub enum TypeFlags {
Instantiable,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NoneType {}

#[derive(Debug, Clone, PartialEq)]
pub struct Any {
pub source: AnySource,
Expand Down Expand Up @@ -144,18 +147,22 @@ pub struct ClassType {
// 1. If the type is inferred from annotation of a parameter or variable that is an instance
// 2. If the type is inferred from a class node then it's an instance
pub is_instance: bool,
// What types are allowed as base classes?
pub base_classes: Vec<ClassType>,
}

impl ClassType {
pub fn new(
details: symbol_table::Class,
type_parameters: Vec<PythonType>,
is_instance: bool,
base_classes: Vec<ClassType>,
) -> Self {
Self {
details,
type_parameters,
is_instance,
base_classes,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Tests the handling of builtins.None in a type annotation.
"""

# Specification: https://typing.readthedocs.io/en/latest/spec/special-types.html#none

from types import NoneType
from typing import Hashable, Iterable, assert_type


# > When used in a type hint, the expression None is considered equivalent to type(None).


def func1(val1: None) -> None:
assert_type(val1, None)
t1: None = None
return None # OK


func1(None) # OK
func1(type(None)) # E

# None is hashable
none1: Hashable = None # OK

# None is not iterable
none2: Iterable = None # E: not iterable


None.__class__ # OK
None.__doc__ # OK
None.__eq__(0) # OK


def func2(val1: type[None]):
assert_type(val1, type[None])


func2(None.__class__) # OK
func2(type(None)) # OK
func2(None) # E: not compatible
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
source: typechecker/src/checker.rs
description: "1: \"\"\"\n2: Tests the handling of builtins.None in a type annotation.\n3: \"\"\"\n4: \n5: # Specification: https://typing.readthedocs.io/en/latest/spec/special-types.html#none\n6: \n7: from types import NoneType\n8: from typing import Hashable, Iterable, assert_type\n9: \n10: \n11: # > When used in a type hint, the expression None is considered equivalent to type(None).\n12: \n13: \n14: def func1(val1: None) -> None:\n15: assert_type(val1, None)\n16: t1: None = None\n17: return None # OK\n18: \n19: \n20: func1(None) # OK\n21: func1(type(None)) # E\n22: \n23: # None is hashable\n24: none1: Hashable = None # OK\n25: \n26: # None is not iterable\n27: none2: Iterable = None # E: not iterable\n28: \n29: \n30: None.__class__ # OK\n31: None.__doc__ # OK\n32: None.__eq__(0) # OK\n33: \n34: \n35: def func2(val1: type[None]):\n36: assert_type(val1, type[None])\n37: \n38: \n39: func2(None.__class__) # OK\n40: func2(type(None)) # OK\n41: func2(None) # E: not compatible\n"
expression: result
---
Line 1: """
Tests the handling of builtins.None in a type annotation.
"""
Expr types in the line --->:
"""
Tests the handling of builtins.None in a type annotation.
""" => (class) str

---
Line 5: from types import NoneType

Expr types in the line --->:
types => Module
NoneType => (class) NoneType

---
Line 6: from typing import Hashable, Iterable, assert_type

Expr types in the line --->:
typing => Module
Hashable => (class) Hashable
Iterable => (class) typing.Iterable[TypeVar[_T_co, ]]
assert_type => (function) Callable[[TypeVar[_T, ], (class) object], TypeVar[_T, ]]

---
Line 12: def func1(val1: None) -> None:

Expr types in the line --->:
func1 => (function) Callable[[None], None]
val1: None => None
None => None
None => None

---
Line 13: assert_type(val1, None)

Expr types in the line --->:
assert_type => (function) Callable[[TypeVar[_T, ], (class) object], TypeVar[_T, ]]
assert_type(val1, None) => TypeVar[_T, ]
val1 => None
None => None

---
Line 14: t1: None = None

Expr types in the line --->:
t1 => None
None => None

---
Line 15: return None # OK

Expr types in the line --->:
None => None

---
Line 18: func1(None) # OK

Expr types in the line --->:
func1 => (function) Callable[[None], None]
func1(None) => None
None => None

---
Line 19: func1(type(None)) # E

Expr types in the line --->:
func1 => (function) Callable[[None], None]
func1(type(None)) => None
type => (class) type
type(None) => (class) type
None => None

---
Line 22: none1: Hashable = None # OK

Expr types in the line --->:
none1 => (class) Hashable
None => None

---
Line 25: none2: Iterable = None # E: not iterable

Expr types in the line --->:
none2 => (class) typing.Iterable[TypeVar[_T_co, ]]
None => None

---
Line 28: None.__class__ # OK

Expr types in the line --->:
None => None
None.__class__ => (function) Callable[[Unknown], (class) builtins.type[(class) Self]]

---
Line 29: None.__doc__ # OK

Expr types in the line --->:
None => None
None.__doc__ => Union[(class) str, None]

---
Line 30: None.__eq__(0) # OK

Expr types in the line --->:
None.__eq__ => (function) Callable[[Unknown, (class) object], (class) bool]
None.__eq__(0) => (class) bool
0 => (class) int

---
Line 33: def func2(val1: type[None]):

Expr types in the line --->:
func2 => (function) Callable[[(class) builtins.type[None]], Unknown]
val1: type[None] => (class) builtins.type[None]
type[None] => (class) type

---
Line 34: assert_type(val1, type[None])

Expr types in the line --->:
assert_type => (function) Callable[[TypeVar[_T, ], (class) object], TypeVar[_T, ]]
assert_type(val1, type[None]) => TypeVar[_T, ]
val1 => (class) builtins.type[None]
type => (class) type
type[None] => (class) type
None => None

---
Line 37: func2(None.__class__) # OK

Expr types in the line --->:
func2 => (function) Callable[[(class) builtins.type[None]], Unknown]
func2(None.__class__) => Unknown
None => None
None.__class__ => (function) Callable[[Unknown], (class) builtins.type[(class) Self]]

---
Line 38: func2(type(None)) # OK

Expr types in the line --->:
func2 => (function) Callable[[(class) builtins.type[None]], Unknown]
func2(type(None)) => Unknown
type => (class) type
type(None) => (class) type
None => None

---
Line 39: func2(None) # E: not compatible

Expr types in the line --->:
func2 => (function) Callable[[(class) builtins.type[None]], Unknown]
func2(None) => Unknown
None => None

---

0 comments on commit 3b0e36d

Please sign in to comment.