Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[red-knot] add more types #11135

Merged
merged 2 commits into from
Apr 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
135 changes: 123 additions & 12 deletions crates/red_knot/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![allow(dead_code)]
use crate::Name;
use ruff_index::{newtype_index, IndexVec};
use rustc_hash::FxHashSet;

#[newtype_index]
pub(crate) struct TypeId;
Expand All @@ -12,16 +13,18 @@ impl TypeId {
}

/// Arena holding all known types
#[derive(Default)]
#[derive(Debug, Default)]
pub(crate) struct TypeEnvironment {
types_by_id: IndexVec<TypeId, Type>,
}

impl TypeEnvironment {
pub(crate) fn add_class(&mut self, name: &str) -> TypeId {
let class = Type::Class(ClassType {
name: Name::new(name),
});
let class = Type::Instance {
instance_of: ClassType {
name: Name::new(name),
},
};
self.types_by_id.push(class)
}

Expand All @@ -37,40 +40,148 @@ impl TypeEnvironment {
}
}

#[derive(Debug)]
pub(crate) enum Type {
Class(ClassType),
/// a specific function
Function(FunctionType),
/// the set of Python objects with this class in their __class__'s method resolution order
Instance {
instance_of: ClassType,
},
Union(UnionType),
Intersection(IntersectionType),
/// the dynamic or gradual type: a statically-unknown set of values
Any,
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
/// the empty set of values
Never,
carljm marked this conversation as resolved.
Show resolved Hide resolved
/// unknown type (no annotation)
/// equivalent to Any, or to object in strict mode
Unknown,
/// name is not bound to any value
Unbound,
// TODO protocols, callable types, overloads, generics, type vars
}

impl Type {
pub(crate) fn name(&self) -> &str {
pub(crate) fn name(&self) -> Option<&str> {
match self {
Type::Class(inner) => inner.name(),
Type::Function(inner) => inner.name(),
Type::Instance { instance_of: class } => Some(class.name()),
Type::Function(func) => Some(func.name()),
Type::Union(_) => None,
Type::Intersection(_) => None,
Type::Any => Some("Any"),
Type::Never => Some("Never"),
Type::Unknown => Some("Unknown"),
Type::Unbound => Some("Unbound"),
}
}

fn display<'a>(&'a self, env: &'a TypeEnvironment) -> DisplayType<'a> {
DisplayType { ty: self, env }
}
}

#[derive(Copy, Clone, Debug)]
struct DisplayType<'a> {
ty: &'a Type,
env: &'a TypeEnvironment,
}

impl std::fmt::Display for DisplayType<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.ty {
Type::Union(inner) => inner.display(f, self.env),
Type::Intersection(inner) => inner.display(f, self.env),
_ => {
let name = self.ty.name().expect("type should have a name");
f.write_str(name)
}
}
}
}

#[derive(Debug)]
pub(crate) struct ClassType {
name: Name,
}

impl ClassType {
pub(crate) fn name(&self) -> &str {
fn name(&self) -> &str {
self.name.as_str()
}
}

#[derive(Debug)]
pub(crate) struct FunctionType {
name: Name,
}

impl FunctionType {
pub(crate) fn name(&self) -> &str {
fn name(&self) -> &str {
self.name.as_str()
}
}

#[derive(Debug)]
pub(crate) struct UnionType {
// the union type includes values in any of these types
elements: FxHashSet<TypeId>,
carljm marked this conversation as resolved.
Show resolved Hide resolved
}

impl UnionType {
fn display(&self, f: &mut std::fmt::Formatter<'_>, env: &TypeEnvironment) -> std::fmt::Result {
f.write_str("(")?;
let mut first = true;
for elem_id in self.elements.iter() {
let ty = env.type_for_id(*elem_id);
if !first {
f.write_str(" | ")?;
};
first = false;
write!(f, "{}", ty.display(env))?;
}
f.write_str(")")
}
}

// Negation types aren't expressible in annotations, and are most likely to arise from type
// narrowing along with intersections (e.g. `if not isinstance(...)`), so we represent them
// directly in intersections rather than as a separate type. This sacrifices some efficiency in the
// case where a Not appears outside an intersection (unclear when that could even happen, but we'd
// have to represent it as a single-element intersection if it did) in exchange for better
// efficiency in the not-within-intersection case.
#[derive(Debug)]
pub(crate) struct IntersectionType {
// the intersection type includes only values in all of these types
positive: FxHashSet<TypeId>,
// negated elements of the intersection, e.g.
negative: FxHashSet<TypeId>,
carljm marked this conversation as resolved.
Show resolved Hide resolved
carljm marked this conversation as resolved.
Show resolved Hide resolved
}

impl IntersectionType {
fn display(&self, f: &mut std::fmt::Formatter<'_>, env: &TypeEnvironment) -> std::fmt::Result {
f.write_str("(")?;
let mut first = true;
for (neg, elem_id) in self
.positive
.iter()
.map(|elem_id| (false, elem_id))
.chain(self.negative.iter().map(|elem_id| (true, elem_id)))
{
let ty = env.type_for_id(*elem_id);
if !first {
f.write_str(" & ")?;
};
first = false;
if neg {
f.write_str("~")?;
};
write!(f, "{}", ty.display(env))?;
}
f.write_str(")")
}
}

#[cfg(test)]
mod tests {
use crate::types::TypeEnvironment;
Expand All @@ -79,13 +190,13 @@ mod tests {
fn add_class() {
let mut env = TypeEnvironment::default();
let cid = env.add_class("C");
assert_eq!(cid.ty(&env).name(), "C");
assert_eq!(cid.ty(&env).name(), Some("C"));
}

#[test]
fn add_function() {
let mut env = TypeEnvironment::default();
let fid = env.add_function("func");
assert_eq!(fid.ty(&env).name(), "func");
assert_eq!(fid.ty(&env).name(), Some("func"));
}
}