Create Definition for all place expressions

This commit is contained in:
Dhruv Manilawala 2025-06-23 15:56:27 +05:30
parent e474f36473
commit ae4f960a47
9 changed files with 533 additions and 448 deletions

View file

@ -338,7 +338,7 @@ class C:
for _, self.y in TupleIterable():
pass
# TODO: We should emit a diagnostic here
# error: [not-iterable]
for self.z in NonIterable():
pass

View file

@ -149,4 +149,7 @@ class Iterable:
async def _():
# revealed: @Todo(async iterables/iterators)
[reveal_type(x) async for x in Iterable()]
# revealed: @Todo(async iterables/iterators)
# revealed: @Todo(async iterables/iterators)
[(reveal_type(x), reveal_type(y)) async for x in Iterable() async for y in Iterable()]
```

View file

@ -858,4 +858,6 @@ Unpacking an empty tuple or list shouldn't raise any diagnostics.
() = ()
[] = ()
() = []
[] = [] = []
() = () = ()
```

View file

@ -32,8 +32,8 @@ use crate::semantic_index::definition::{
};
use crate::semantic_index::expression::{Expression, ExpressionKind};
use crate::semantic_index::place::{
FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr,
PlaceExprWithFlags, PlaceTableBuilder, Scope, ScopeId, ScopeKind, ScopedPlaceId,
FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, PlaceExprWithFlags,
PlaceTableBuilder, Scope, ScopeId, ScopeKind, ScopedPlaceId,
};
use crate::semantic_index::predicate::{
PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, ScopedPredicateId,
@ -1872,11 +1872,9 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
// We will check the target expressions and then delete them.
walk_stmt(self, stmt);
for target in targets {
if let Ok(target) = PlaceExpr::try_from(target) {
let place_id = self.add_place(PlaceExprWithFlags::new(target));
self.current_place_table().mark_place_used(place_id);
self.delete_binding(place_id);
}
let place_id = self.add_place(PlaceExprWithFlags::new(target));
self.current_place_table().mark_place_used(place_id);
self.delete_binding(place_id);
}
}
ast::Stmt::Expr(ast::StmtExpr {
@ -1908,126 +1906,125 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
ast::Expr::Name(ast::ExprName { ctx, .. })
| ast::Expr::Attribute(ast::ExprAttribute { ctx, .. })
| ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => {
if let Ok(place_expr) = PlaceExpr::try_from(expr) {
let mut place_expr = PlaceExprWithFlags::new(place_expr);
if self.is_method_of_class().is_some()
&& place_expr.is_instance_attribute_candidate()
{
// We specifically mark attribute assignments to the first parameter of a method,
// i.e. typically `self` or `cls`.
let accessed_object_refers_to_first_parameter = self
.current_first_parameter_name
.is_some_and(|fst| place_expr.expr.root_name() == fst);
let mut place_expr = PlaceExprWithFlags::new(expr);
if self.is_method_of_class().is_some()
&& place_expr.is_instance_attribute_candidate()
{
// We specifically mark attribute assignments to the first parameter of a method,
// i.e. typically `self` or `cls`.
let accessed_object_refers_to_first_parameter =
self.current_first_parameter_name.is_some_and(|fst| {
place_expr.expr.root_name().is_some_and(|name| name == fst)
});
if accessed_object_refers_to_first_parameter && place_expr.is_member() {
place_expr.mark_instance_attribute();
if accessed_object_refers_to_first_parameter && place_expr.is_member() {
place_expr.mark_instance_attribute();
}
}
let (is_use, is_definition) = match (ctx, self.current_assignment()) {
(ast::ExprContext::Store, Some(CurrentAssignment::AugAssign(_))) => {
// For augmented assignment, the target expression is also used.
(true, true)
}
(ast::ExprContext::Load, _) => (true, false),
(ast::ExprContext::Store, _) => (false, true),
(ast::ExprContext::Del, _) => (true, true),
(ast::ExprContext::Invalid, _) => (false, false),
};
let place_id = self.add_place(place_expr);
if is_use {
self.mark_place_used(place_id);
let use_id = self.current_ast_ids().record_use(expr);
self.current_use_def_map_mut()
.record_use(place_id, use_id, node_key);
}
if is_definition {
match self.current_assignment() {
Some(CurrentAssignment::Assign { node, unpack }) => {
self.add_definition(
place_id,
AssignmentDefinitionNodeRef {
unpack,
value: &node.value,
target: expr,
},
);
}
}
let (is_use, is_definition) = match (ctx, self.current_assignment()) {
(ast::ExprContext::Store, Some(CurrentAssignment::AugAssign(_))) => {
// For augmented assignment, the target expression is also used.
(true, true)
Some(CurrentAssignment::AnnAssign(ann_assign)) => {
self.add_standalone_type_expression(&ann_assign.annotation);
self.add_definition(
place_id,
AnnotatedAssignmentDefinitionNodeRef {
node: ann_assign,
annotation: &ann_assign.annotation,
value: ann_assign.value.as_deref(),
target: expr,
},
);
}
(ast::ExprContext::Load, _) => (true, false),
(ast::ExprContext::Store, _) => (false, true),
(ast::ExprContext::Del, _) => (true, true),
(ast::ExprContext::Invalid, _) => (false, false),
};
let place_id = self.add_place(place_expr);
if is_use {
self.mark_place_used(place_id);
let use_id = self.current_ast_ids().record_use(expr);
self.current_use_def_map_mut()
.record_use(place_id, use_id, node_key);
}
if is_definition {
match self.current_assignment() {
Some(CurrentAssignment::Assign { node, unpack }) => {
self.add_definition(
place_id,
AssignmentDefinitionNodeRef {
unpack,
value: &node.value,
target: expr,
},
);
}
Some(CurrentAssignment::AnnAssign(ann_assign)) => {
self.add_standalone_type_expression(&ann_assign.annotation);
self.add_definition(
place_id,
AnnotatedAssignmentDefinitionNodeRef {
node: ann_assign,
annotation: &ann_assign.annotation,
value: ann_assign.value.as_deref(),
target: expr,
},
);
}
Some(CurrentAssignment::AugAssign(aug_assign)) => {
self.add_definition(place_id, aug_assign);
}
Some(CurrentAssignment::For { node, unpack }) => {
self.add_definition(
place_id,
ForStmtDefinitionNodeRef {
unpack,
iterable: &node.iter,
target: expr,
is_async: node.is_async,
},
);
}
Some(CurrentAssignment::Named(named)) => {
// TODO(dhruvmanila): If the current scope is a comprehension, then the
// named expression is implicitly nonlocal. This is yet to be
// implemented.
self.add_definition(place_id, named);
}
Some(CurrentAssignment::Comprehension {
unpack,
node,
first,
}) => {
self.add_definition(
place_id,
ComprehensionDefinitionNodeRef {
unpack,
iterable: &node.iter,
target: expr,
first,
is_async: node.is_async,
},
);
}
Some(CurrentAssignment::WithItem {
item,
is_async,
unpack,
}) => {
self.add_definition(
place_id,
WithItemDefinitionNodeRef {
unpack,
context_expr: &item.context_expr,
target: expr,
is_async,
},
);
}
None => {}
Some(CurrentAssignment::AugAssign(aug_assign)) => {
self.add_definition(place_id, aug_assign);
}
Some(CurrentAssignment::For { node, unpack }) => {
self.add_definition(
place_id,
ForStmtDefinitionNodeRef {
unpack,
iterable: &node.iter,
target: expr,
is_async: node.is_async,
},
);
}
Some(CurrentAssignment::Named(named)) => {
// TODO(dhruvmanila): If the current scope is a comprehension, then the
// named expression is implicitly nonlocal. This is yet to be
// implemented.
self.add_definition(place_id, named);
}
Some(CurrentAssignment::Comprehension {
unpack,
node,
first,
}) => {
self.add_definition(
place_id,
ComprehensionDefinitionNodeRef {
unpack,
iterable: &node.iter,
target: expr,
first,
is_async: node.is_async,
},
);
}
Some(CurrentAssignment::WithItem {
item,
is_async,
unpack,
}) => {
self.add_definition(
place_id,
WithItemDefinitionNodeRef {
unpack,
context_expr: &item.context_expr,
target: expr,
is_async,
},
);
}
None => {}
}
}
if let Some(unpack_position) = self
.current_assignment_mut()
.and_then(CurrentAssignment::unpack_position_mut)
{
*unpack_position = UnpackPosition::Other;
}
if let Some(unpack_position) = self
.current_assignment_mut()
.and_then(CurrentAssignment::unpack_position_mut)
{
*unpack_position = UnpackPosition::Other;
}
// Track reachability of attribute expressions to silence `unresolved-attribute`

View file

@ -1,4 +1,3 @@
use std::convert::Infallible;
use std::hash::{Hash, Hasher};
use std::ops::Range;
@ -7,25 +6,80 @@ use hashbrown::hash_table::Entry;
use ruff_db::files::File;
use ruff_db::parsed::ParsedModuleRef;
use ruff_index::{IndexVec, newtype_index};
use ruff_python_ast as ast;
use ruff_python_ast::name::Name;
use ruff_python_ast::{self as ast, ExprRef};
use rustc_hash::FxHasher;
use smallvec::{SmallVec, smallvec};
use crate::Db;
use crate::ast_node_ref::AstNodeRef;
use crate::node_key::NodeKey;
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
use crate::semantic_index::reachability_constraints::ScopedReachabilityConstraintId;
use crate::semantic_index::{PlaceSet, SemanticIndex, semantic_index};
/// The root of a place expression.
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum PlaceRoot {
/// A name, e.g. `x` in `x = 1` or `x.y = 1`
Name(Name),
/// An arbitrary expression, e.g. `a()` in `a() = 1` or `a().b = 1`
Expr(ExpressionNodeKey),
}
impl PlaceRoot {
const fn is_name(&self) -> bool {
matches!(self, PlaceRoot::Name(_))
}
pub(crate) const fn as_name(&self) -> Option<&Name> {
match self {
PlaceRoot::Name(name) => Some(name),
PlaceRoot::Expr(_) => None,
}
}
#[track_caller]
fn expect_name(&self) -> &Name {
match self {
PlaceRoot::Name(name) => name,
PlaceRoot::Expr(_) => panic!("expected PlaceRoot::Name, found PlaceRoot::Expr"),
}
}
}
impl From<&ast::Expr> for PlaceRoot {
fn from(expr: &ast::Expr) -> Self {
match expr {
ast::Expr::Name(name) => PlaceRoot::Name(name.id.clone()),
_ => PlaceRoot::Expr(ExpressionNodeKey::from(expr)),
}
}
}
impl std::fmt::Display for PlaceRoot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PlaceRoot::Name(name) => write!(f, "{name}"),
// TODO: How to display this?
PlaceRoot::Expr(_) => f.write_str("<expr>"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum PlaceExprSubSegment {
/// A member access, e.g. `.y` in `x.y`
Member(ast::name::Name),
/// A name member access, e.g. `.y` in `x.y`
Member(Name),
/// An integer-based index access, e.g. `[1]` in `x[1]`
IntSubscript(ast::Int),
/// A string-based index access, e.g. `["foo"]` in `x["foo"]`
StringSubscript(String),
/// An arbitrary subscript, e.g. `[y]` in `x[y]`
// TODO: Specific expressions that are valid in this position can be extracted as separate enum
// variants in the future to provide precise information like `NameSubscript` for the above
// example.
AnySubscript(ExpressionNodeKey),
}
impl PlaceExprSubSegment {
@ -40,137 +94,33 @@ impl PlaceExprSubSegment {
/// An expression that can be the target of a `Definition`.
#[derive(Eq, PartialEq, Debug)]
pub struct PlaceExpr {
root_name: Name,
/// The root of the place expression, which can be a [`Name`] or any arbitrary expression.
root: PlaceRoot,
/// The sub-segments of the place expression.
///
/// This is relevant only for attribute and subscript expressions, e.g. `x.y` or `x[0].y`. This
/// will be empty for simple names like `x` or `y`.
sub_segments: SmallVec<[PlaceExprSubSegment; 1]>,
}
impl std::fmt::Display for PlaceExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.root_name)?;
for segment in &self.sub_segments {
match segment {
PlaceExprSubSegment::Member(name) => write!(f, ".{name}")?,
PlaceExprSubSegment::IntSubscript(int) => write!(f, "[{int}]")?,
PlaceExprSubSegment::StringSubscript(string) => write!(f, "[\"{string}\"]")?,
}
}
Ok(())
}
}
impl TryFrom<&ast::name::Name> for PlaceExpr {
type Error = Infallible;
fn try_from(name: &ast::name::Name) -> Result<Self, Infallible> {
Ok(PlaceExpr::name(name.clone()))
}
}
impl TryFrom<ast::name::Name> for PlaceExpr {
type Error = Infallible;
fn try_from(name: ast::name::Name) -> Result<Self, Infallible> {
Ok(PlaceExpr::name(name))
}
}
impl TryFrom<&ast::ExprAttribute> for PlaceExpr {
type Error = ();
fn try_from(attr: &ast::ExprAttribute) -> Result<Self, ()> {
let mut place = PlaceExpr::try_from(&*attr.value)?;
place
.sub_segments
.push(PlaceExprSubSegment::Member(attr.attr.id.clone()));
Ok(place)
}
}
impl TryFrom<ast::ExprAttribute> for PlaceExpr {
type Error = ();
fn try_from(attr: ast::ExprAttribute) -> Result<Self, ()> {
let mut place = PlaceExpr::try_from(&*attr.value)?;
place
.sub_segments
.push(PlaceExprSubSegment::Member(attr.attr.id));
Ok(place)
}
}
impl TryFrom<&ast::ExprSubscript> for PlaceExpr {
type Error = ();
fn try_from(subscript: &ast::ExprSubscript) -> Result<Self, ()> {
let mut place = PlaceExpr::try_from(&*subscript.value)?;
match &*subscript.slice {
ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(index),
..
}) => {
place
.sub_segments
.push(PlaceExprSubSegment::IntSubscript(index.clone()));
}
ast::Expr::StringLiteral(string) => {
place
.sub_segments
.push(PlaceExprSubSegment::StringSubscript(
string.value.to_string(),
));
}
_ => {
return Err(());
}
}
Ok(place)
}
}
impl TryFrom<ast::ExprSubscript> for PlaceExpr {
type Error = ();
fn try_from(subscript: ast::ExprSubscript) -> Result<Self, ()> {
PlaceExpr::try_from(&subscript)
}
}
impl TryFrom<&ast::Expr> for PlaceExpr {
type Error = ();
fn try_from(expr: &ast::Expr) -> Result<Self, ()> {
match expr {
ast::Expr::Name(name) => Ok(PlaceExpr::name(name.id.clone())),
ast::Expr::Attribute(attr) => PlaceExpr::try_from(attr),
ast::Expr::Subscript(subscript) => PlaceExpr::try_from(subscript),
_ => Err(()),
}
}
}
impl TryFrom<ast::ExprRef<'_>> for PlaceExpr {
type Error = ();
fn try_from(expr: ast::ExprRef) -> Result<Self, ()> {
match expr {
ast::ExprRef::Name(name) => Ok(PlaceExpr::name(name.id.clone())),
ast::ExprRef::Attribute(attr) => PlaceExpr::try_from(attr),
ast::ExprRef::Subscript(subscript) => PlaceExpr::try_from(subscript),
_ => Err(()),
}
}
}
impl PlaceExpr {
pub(crate) fn name(name: Name) -> Self {
Self {
root_name: name,
root: PlaceRoot::Name(name),
sub_segments: smallvec![],
}
}
pub(crate) fn root_name(&self) -> &Name {
&self.root_name
fn from_expr(expr: ExpressionNodeKey) -> Self {
Self {
root: PlaceRoot::Expr(expr),
sub_segments: smallvec![],
}
}
pub(super) fn root_name(&self) -> Option<&Name> {
self.root.as_name()
}
pub(crate) fn sub_segments(&self) -> &[PlaceExprSubSegment] {
@ -179,26 +129,26 @@ impl PlaceExpr {
pub(crate) fn as_name(&self) -> Option<&Name> {
if self.is_name() {
Some(&self.root_name)
Some(self.root.expect_name())
} else {
None
}
}
/// Assumes that the place expression is a name.
/// Assumes that the place expression is a name, panics if it is not.
#[track_caller]
pub(crate) fn expect_name(&self) -> &Name {
debug_assert_eq!(self.sub_segments.len(), 0);
&self.root_name
self.root.expect_name()
}
/// Is the place just a name?
pub fn is_name(&self) -> bool {
self.sub_segments.is_empty()
self.root.is_name() && self.sub_segments.is_empty()
}
pub fn is_name_and(&self, f: impl FnOnce(&str) -> bool) -> bool {
self.is_name() && f(&self.root_name)
self.as_name().is_some_and(|name| f(name.as_str()))
}
/// Does the place expression have the form `<object>.member`?
@ -208,6 +158,19 @@ impl PlaceExpr {
.is_some_and(|last| last.as_member().is_some())
}
/// Returns `true` if this is a valid place expression that can be used in a definition.
pub(crate) fn is_valid(&self) -> bool {
self.root.is_name()
&& self.sub_segments.iter().all(|segment| {
matches!(
segment,
PlaceExprSubSegment::Member(_)
| PlaceExprSubSegment::IntSubscript(_)
| PlaceExprSubSegment::StringSubscript(_)
)
})
}
fn root_exprs(&self) -> RootExprs<'_> {
RootExprs {
expr_ref: self.into(),
@ -216,6 +179,77 @@ impl PlaceExpr {
}
}
impl From<&ast::Expr> for PlaceExpr {
fn from(expr: &ast::Expr) -> Self {
match expr {
ast::Expr::Name(name) => PlaceExpr::name(name.id.clone()),
ast::Expr::Attribute(attr) => PlaceExpr::from(attr),
ast::Expr::Subscript(subscript) => PlaceExpr::from(subscript),
_ => PlaceExpr::from_expr(ExpressionNodeKey::from(expr)),
}
}
}
impl From<ExprRef<'_>> for PlaceExpr {
fn from(expr: ExprRef<'_>) -> Self {
match expr {
ExprRef::Name(name) => PlaceExpr::name(name.id.clone()),
ExprRef::Attribute(attr) => PlaceExpr::from(attr),
ExprRef::Subscript(subscript) => PlaceExpr::from(subscript),
_ => PlaceExpr::from_expr(ExpressionNodeKey::from(expr)),
}
}
}
impl From<&ast::name::Name> for PlaceExpr {
fn from(name: &ast::name::Name) -> Self {
PlaceExpr::name(name.clone())
}
}
impl From<&ast::ExprAttribute> for PlaceExpr {
fn from(value: &ast::ExprAttribute) -> Self {
let mut place = PlaceExpr::from(&*value.value);
place
.sub_segments
.push(PlaceExprSubSegment::Member(value.attr.id.clone()));
place
}
}
impl From<&ast::ExprSubscript> for PlaceExpr {
fn from(subscript: &ast::ExprSubscript) -> Self {
let mut place = PlaceExpr::from(&*subscript.value);
place.sub_segments.push(match &*subscript.slice {
ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(index),
..
}) => PlaceExprSubSegment::IntSubscript(index.clone()),
ast::Expr::StringLiteral(string) => {
PlaceExprSubSegment::StringSubscript(string.value.to_string())
}
expr => PlaceExprSubSegment::AnySubscript(ExpressionNodeKey::from(expr)),
});
place
}
}
impl std::fmt::Display for PlaceExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.root)?;
for segment in &self.sub_segments {
match segment {
PlaceExprSubSegment::Member(name) => write!(f, ".{name}")?,
PlaceExprSubSegment::IntSubscript(int) => write!(f, "[{int}]")?,
PlaceExprSubSegment::StringSubscript(string) => write!(f, "[\"{string}\"]")?,
// TODO: How to display this? Raw text or source generator?
PlaceExprSubSegment::AnySubscript(_) => write!(f, "[<expr>]")?,
}
}
Ok(())
}
}
/// A [`PlaceExpr`] with flags, e.g. whether it is used, bound, an instance attribute, etc.
#[derive(Eq, PartialEq, Debug)]
pub struct PlaceExprWithFlags {
@ -230,9 +264,9 @@ impl std::fmt::Display for PlaceExprWithFlags {
}
impl PlaceExprWithFlags {
pub(crate) fn new(expr: PlaceExpr) -> Self {
pub(crate) fn new(expr: &ast::Expr) -> Self {
PlaceExprWithFlags {
expr,
expr: PlaceExpr::from(expr),
flags: PlaceFlags::empty(),
}
}
@ -263,8 +297,8 @@ impl PlaceExprWithFlags {
/// parameter of the method (i.e. `self`). To answer those questions,
/// use [`Self::as_instance_attribute`].
pub(super) fn as_instance_attribute_candidate(&self) -> Option<&Name> {
if self.expr.sub_segments.len() == 1 {
self.expr.sub_segments[0].as_member()
if let [sub_segment] = self.expr.sub_segments.as_slice() {
sub_segment.as_member()
} else {
None
}
@ -340,28 +374,53 @@ impl PlaceExprWithFlags {
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
pub struct PlaceExprRef<'a> {
pub(crate) root_name: &'a Name,
pub(crate) enum PlaceRootRef<'a> {
Name(&'a Name),
Expr(ExpressionNodeKey),
}
impl<'a> From<&'a PlaceRoot> for PlaceRootRef<'a> {
fn from(root: &'a PlaceRoot) -> Self {
match root {
PlaceRoot::Name(name) => PlaceRootRef::Name(name),
PlaceRoot::Expr(expr) => PlaceRootRef::Expr(*expr),
}
}
}
impl PartialEq<PlaceRoot> for PlaceRootRef<'_> {
fn eq(&self, other: &PlaceRoot) -> bool {
match (self, other) {
(PlaceRootRef::Name(name), PlaceRoot::Name(other_name)) => name == &other_name,
(PlaceRootRef::Expr(expr), PlaceRoot::Expr(other_expr)) => expr == other_expr,
_ => false,
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
pub(crate) struct PlaceExprRef<'a> {
pub(crate) root: PlaceRootRef<'a>,
/// Sub-segments is empty for a simple target (e.g. `foo`).
pub(crate) sub_segments: &'a [PlaceExprSubSegment],
}
impl PartialEq<PlaceExpr> for PlaceExprRef<'_> {
fn eq(&self, other: &PlaceExpr) -> bool {
self.root_name == &other.root_name && self.sub_segments == &other.sub_segments[..]
self.root == other.root && self.sub_segments == &other.sub_segments[..]
}
}
impl PartialEq<PlaceExprRef<'_>> for PlaceExpr {
fn eq(&self, other: &PlaceExprRef<'_>) -> bool {
&self.root_name == other.root_name && &self.sub_segments[..] == other.sub_segments
other.root == self.root && &self.sub_segments[..] == other.sub_segments
}
}
impl<'e> From<&'e PlaceExpr> for PlaceExprRef<'e> {
fn from(expr: &'e PlaceExpr) -> Self {
PlaceExprRef {
root_name: &expr.root_name,
root: PlaceRootRef::from(&expr.root),
sub_segments: &expr.sub_segments,
}
}
@ -381,7 +440,7 @@ impl<'e> Iterator for RootExprs<'e> {
}
self.len -= 1;
Some(PlaceExprRef {
root_name: self.expr_ref.root_name,
root: self.expr_ref.root,
sub_segments: &self.expr_ref.sub_segments[..self.len],
})
}
@ -713,7 +772,10 @@ impl PlaceTable {
// Special case for simple names (e.g. "foo"). Only hash the name so
// that a lookup by name can find it (see `place_by_name`).
if place_expr.sub_segments.is_empty() {
place_expr.root_name.as_str().hash(&mut hasher);
match place_expr.root {
PlaceRootRef::Name(name) => name.as_str().hash(&mut hasher),
PlaceRootRef::Expr(expr) => expr.hash(&mut hasher),
}
} else {
place_expr.hash(&mut hasher);
}

View file

@ -122,7 +122,10 @@ impl HasType for ast::ExprRef<'_> {
let scope = file_scope.to_scope_id(model.db, model.file);
let expression_id = self.scoped_expression_id(model.db, scope);
infer_scope_types(model.db, scope).expression_type(expression_id)
tracing::info!("ExprRef: {:?}", self);
let ty = infer_scope_types(model.db, scope).expression_type(expression_id);
tracing::info!("Inferred type: {}", ty.display(model.db));
ty
}
}

View file

@ -358,8 +358,8 @@ fn unpack_cycle_recover<'db>(
salsa::CycleRecoveryAction::Iterate
}
fn unpack_cycle_initial<'db>(_db: &'db dyn Db, _unpack: Unpack<'db>) -> UnpackResult<'db> {
UnpackResult::cycle_fallback(Type::Never)
fn unpack_cycle_initial<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> UnpackResult<'db> {
UnpackResult::cycle_fallback(unpack.value_scope(db), Type::Never)
}
/// Returns the type of the nearest enclosing class for the given scope.
@ -513,11 +513,25 @@ impl<'db> TypeInference<'db> {
)
}
pub(super) fn extend(&mut self, inference: &TypeInference<'db>) {
debug_assert_eq!(self.scope, inference.scope);
self.bindings.extend(inference.bindings.iter());
self.declarations.extend(inference.declarations.iter());
self.expressions.extend(inference.expressions.iter());
self.deferred.extend(inference.deferred.iter());
self.cycle_fallback_type = self.cycle_fallback_type.or(inference.cycle_fallback_type);
}
pub(super) fn set_diagnostics(&mut self, diagnostics: TypeCheckDiagnostics) {
self.diagnostics = diagnostics;
}
pub(crate) fn diagnostics(&self) -> &TypeCheckDiagnostics {
&self.diagnostics
}
fn shrink_to_fit(&mut self) {
pub(super) fn shrink_to_fit(&mut self) {
self.expressions.shrink_to_fit();
self.bindings.shrink_to_fit();
self.declarations.shrink_to_fit();
@ -667,19 +681,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
fn extend(&mut self, inference: &TypeInference<'db>) {
debug_assert_eq!(self.types.scope, inference.scope);
self.types.bindings.extend(inference.bindings.iter());
self.types
.declarations
.extend(inference.declarations.iter());
self.types.expressions.extend(inference.expressions.iter());
self.types.deferred.extend(inference.deferred.iter());
self.types.extend(inference);
self.context.extend(inference.diagnostics());
self.types.cycle_fallback_type = self
.types
.cycle_fallback_type
.or(inference.cycle_fallback_type);
}
fn file(&self) -> File {
@ -1637,11 +1640,35 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
ty.inner_type()
});
if !bound_ty.is_assignable_to(db, declared_ty) {
if let Some(
attr_expr @ ast::ExprAttribute {
value: object,
ctx: ExprContext::Store,
attr,
..
},
) = node.as_expr_attribute()
{
if !self.validate_attribute_assignment(
attr_expr,
self.expression_type(object),
attr.id(),
bound_ty,
true,
) {
// If the attribute assignment is invalid, we still want to bind the type
// to the attribute, so we don't report it again.
if !declared_ty.is_unknown() {
bound_ty = declared_ty;
}
}
} else if !bound_ty.is_assignable_to(db, declared_ty) {
report_invalid_assignment(&self.context, node, declared_ty, bound_ty);
// allow declarations to override inference in case of invalid assignment
bound_ty = declared_ty;
}
// In the following cases, the bound type may not be the same as the RHS value type.
if let AnyNodeRef::ExprAttribute(ast::ExprAttribute { value, attr, .. }) = node {
let value_ty = self
@ -2724,14 +2751,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
for item in items {
let target = item.optional_vars.as_deref();
if let Some(target) = target {
self.infer_target(target, &item.context_expr, |builder, context_expr| {
// TODO: `infer_with_statement_definition` reports a diagnostic if `ctx_manager_ty` isn't a context manager
// but only if the target is a name. We should report a diagnostic here if the target isn't a name:
// `with not_context_manager as a.x: ...
builder
.infer_standalone_expression(context_expr)
.enter(builder.db())
});
self.infer_target(target, &item.context_expr, false);
} else {
// Call into the context expression inference to validate that it evaluates
// to a valid context manager.
@ -2753,23 +2773,25 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let target = with_item.target(self.module());
let target_ty = if with_item.is_async() {
let _context_expr_ty = self.infer_standalone_expression(context_expr);
let _context_expr_type = self.infer_standalone_expression(context_expr);
todo_type!("async `with` statement")
} else {
match with_item.target_kind() {
TargetKind::Sequence(unpack_position, unpack) => {
let unpacked = infer_unpack_types(self.db(), unpack);
let target_ast_id = target.scoped_expression_id(self.db(), self.scope());
// Only copy the inference results if this is the first assignment to avoid any
// duplication e.g., diagnostics.
if unpack_position == UnpackPosition::First {
self.context.extend(unpacked.diagnostics());
self.extend(unpacked.types());
}
let target_ast_id = target.scoped_expression_id(self.db(), self.scope());
unpacked.expression_type(target_ast_id)
}
TargetKind::Single => {
let context_expr_ty = self.infer_standalone_expression(context_expr);
let context_expr_type = self.infer_standalone_expression(context_expr);
self.infer_context_expression(
context_expr,
context_expr_ty,
context_expr_type,
with_item.is_async(),
)
}
@ -3186,30 +3208,48 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} = assignment;
for target in targets {
self.infer_target(target, value, |builder, value_expr| {
builder.infer_standalone_expression(value_expr)
});
self.infer_target(target, value, false);
}
}
/// Infer the (definition) types involved in a `target` expression.
///
/// This is used for assignment statements, for statements, etc. with a single or multiple
/// targets (unpacking). If `target` is an attribute expression, we check that the assignment
/// is valid. For 'target's that are definitions, this check happens elsewhere.
/// targets (unpacking).
///
/// The `infer_value_expr` function is used to infer the type of the `value` expression which
/// are not `Name` expressions. The returned type is the one that is eventually assigned to the
/// `target`.
fn infer_target<F>(&mut self, target: &ast::Expr, value: &ast::Expr, infer_value_expr: F)
where
F: Fn(&mut TypeInferenceBuilder<'db, '_>, &ast::Expr) -> Type<'db>,
{
let assigned_ty = match target {
ast::Expr::Name(_) => None,
_ => Some(infer_value_expr(self, value)),
};
self.infer_target_impl(target, assigned_ty);
/// # Panics
///
/// Panics if the `value` expression is not a standalone expression.
fn infer_target(
&mut self,
target: &ast::Expr,
value: &ast::Expr,
is_first_comprehension: bool,
) {
// TODO: Starred target
// TODO: Subscript target
match target {
ast::Expr::Name(name) => self.infer_definition(name),
ast::Expr::Attribute(attribute) => self.infer_definition(attribute),
ast::Expr::List(ast::ExprList { elts, .. })
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
for element in elts {
self.infer_target(element, value, is_first_comprehension);
}
// If the target list / tuple is empty, we still need to infer the value expression
// to store its type.
if elts.is_empty() {
self.infer_standalone_expression(value);
}
}
_ => {
// TODO: Remove this once we handle all possible assignment targets
if !is_first_comprehension {
self.infer_standalone_expression(value);
}
self.infer_expression(target);
}
}
}
/// Make sure that the attribute assignment `obj.attribute = value` is valid.
@ -3695,71 +3735,31 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
fn infer_target_impl(&mut self, target: &ast::Expr, assigned_ty: Option<Type<'db>>) {
match target {
ast::Expr::Name(name) => self.infer_definition(name),
ast::Expr::List(ast::ExprList { elts, .. })
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
let mut assigned_tys = match assigned_ty {
Some(Type::Tuple(tuple)) => Either::Left(tuple.tuple(self.db()).all_elements()),
Some(_) | None => Either::Right(std::iter::empty()),
};
for element in elts {
self.infer_target_impl(element, assigned_tys.next());
}
}
ast::Expr::Attribute(
attr_expr @ ast::ExprAttribute {
value: object,
ctx: ExprContext::Store,
attr,
..
},
) => {
self.store_expression_type(target, assigned_ty.unwrap_or(Type::unknown()));
let object_ty = self.infer_expression(object);
if let Some(assigned_ty) = assigned_ty {
self.validate_attribute_assignment(
attr_expr,
object_ty,
attr.id(),
assigned_ty,
true,
);
}
}
_ => {
// TODO: Remove this once we handle all possible assignment targets.
self.infer_expression(target);
}
}
}
fn infer_assignment_definition(
&mut self,
assignment: &AssignmentDefinitionKind<'db>,
definition: Definition<'db>,
) {
let value = assignment.value(self.module());
let target = assignment.target(self.module());
let mut target_ty = match assignment.target_kind() {
let target_ty = match assignment.target_kind() {
TargetKind::Sequence(unpack_position, unpack) => {
let unpacked = infer_unpack_types(self.db(), unpack);
// Only copy the diagnostics if this is the first assignment to avoid duplicating the
// unpack assignments.
// Only copy the inference results if this is the first assignment to avoid any
// duplication e.g., diagnostics.
if unpack_position == UnpackPosition::First {
self.context.extend(unpacked.diagnostics());
self.extend(unpacked.types());
}
let target_ast_id = target.scoped_expression_id(self.db(), self.scope());
unpacked.expression_type(target_ast_id)
}
TargetKind::Single => {
let value_ty = self.infer_standalone_expression(value);
let value = assignment.value(self.module());
// The type of the value expression always needs to be inferred even though there
// are special cases that doesn't require this because of the invariant that every
// expression should have a type inferred for it.
let value_type = self.infer_standalone_expression(value);
// `TYPE_CHECKING` is a special variable that should only be assigned `False`
// at runtime, but is always considered `True` in type checking.
@ -3772,20 +3772,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
report_invalid_type_checking_constant(&self.context, target.into());
}
Type::BooleanLiteral(true)
} else if let Some(special_form) = target.as_name_expr().and_then(|name| {
SpecialFormType::try_from_file_and_name(self.db(), self.file(), &name.id)
}) {
Type::SpecialForm(special_form)
} else if self.in_stub() && value.is_ellipsis_literal_expr() {
Type::unknown()
} else {
value_ty
value_type
}
}
};
if let Some(special_form) = target.as_name_expr().and_then(|name| {
SpecialFormType::try_from_file_and_name(self.db(), self.file(), &name.id)
}) {
target_ty = Type::SpecialForm(special_form);
}
self.store_expression_type(target, target_ty);
self.add_binding(target.into(), definition, target_ty);
}
@ -3882,7 +3880,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// If the target of an assignment is not one of the place expressions we support,
// then they are not definitions, so we can only be here if the target is in a form supported as a place expression.
// In this case, we can simply store types in `target` below, instead of calling `infer_expression` (which would return `Never`).
debug_assert!(PlaceExpr::try_from(target).is_ok());
// debug_assert!(PlaceExpr::try_from(target).is_ok());
if let Some(value) = value {
let inferred_ty = self.infer_expression(value);
@ -4044,15 +4042,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
is_async: _,
} = for_statement;
self.infer_target(target, iter, |builder, iter_expr| {
// TODO: `infer_for_statement_definition` reports a diagnostic if `iter_ty` isn't iterable
// but only if the target is a name. We should report a diagnostic here if the target isn't a name:
// `for a.x in not_iterable: ...
builder
.infer_standalone_expression(iter_expr)
.iterate(builder.db())
});
self.infer_target(target, iter, false);
self.infer_body(body);
self.infer_body(orelse);
}
@ -4072,8 +4062,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
match for_stmt.target_kind() {
TargetKind::Sequence(unpack_position, unpack) => {
let unpacked = infer_unpack_types(self.db(), unpack);
// Only copy the inference results if this is the first assignment to avoid any
// duplication e.g., diagnostics.
if unpack_position == UnpackPosition::First {
self.context.extend(unpacked.diagnostics());
self.extend(unpacked.types());
}
let target_ast_id = target.scoped_expression_id(self.db(), self.scope());
unpacked.expression_type(target_ast_id)
@ -5054,21 +5046,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
is_async: _,
} = comprehension;
self.infer_target(target, iter, |builder, iter_expr| {
// TODO: `infer_comprehension_definition` reports a diagnostic if `iter_ty` isn't iterable
// but only if the target is a name. We should report a diagnostic here if the target isn't a name:
// `[... for a.x in not_iterable]
if is_first {
infer_same_file_expression_type(
builder.db(),
builder.index.expression(iter_expr),
builder.module(),
)
} else {
builder.infer_standalone_expression(iter_expr)
}
.iterate(builder.db())
});
self.infer_target(target, iter, is_first);
for expr in ifs {
self.infer_expression(expr);
}
@ -5079,8 +5057,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
comprehension: &ComprehensionDefinitionKind<'db>,
definition: Definition<'db>,
) {
let iterable = comprehension.iterable(self.module());
let target = comprehension.target(self.module());
let iterable = comprehension.iterable(self.module());
let mut infer_iterable_type = || {
let expression = self.index.expression(iterable);
@ -5092,7 +5070,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// (2) We must *not* call `self.extend()` on the result of the type inference,
// because `ScopedExpressionId`s are only meaningful within their own scope, so
// we'd add types for random wrong expressions in the current scope
if comprehension.is_first() && target.is_name_expr() {
if comprehension.is_first() {
let lookup_scope = self
.index
.parent_scope_id(self.scope().file_scope_id(self.db()))
@ -5119,7 +5097,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
TargetKind::Sequence(unpack_position, unpack) => {
let unpacked = infer_unpack_types(self.db(), unpack);
if unpack_position == UnpackPosition::First {
self.context.extend(unpacked.diagnostics());
if comprehension.is_first() {
self.context.extend(unpacked.types().diagnostics());
} else {
self.extend(unpacked.types());
}
}
let target_ast_id =
target.scoped_expression_id(self.db(), unpack.target_scope(self.db()));
@ -5135,10 +5117,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
};
self.types.expressions.insert(
target.scoped_expression_id(self.db(), self.scope()),
target_type,
);
self.store_expression_type(target, target_type);
self.add_binding(target.into(), definition, target_type);
}
@ -5954,10 +5933,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
None
}
Some(expr) => match PlaceExpr::try_from(expr) {
Ok(place_expr) => place_table(db, scope).place_id_by_expr(&place_expr),
Err(()) => None,
},
Some(expr) => {
let place_expr = PlaceExpr::from(expr);
if !place_expr.is_valid() {
return None;
}
place_table(db, scope).place_id_by_expr(&place_expr)
}
};
match return_ty {
@ -6116,7 +6098,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
id: symbol_name,
ctx: _,
} = name_node;
let Ok(expr) = PlaceExpr::try_from(symbol_name);
let expr = PlaceExpr::from(symbol_name);
let db = self.db();
let (resolved, constraint_keys) =
@ -6490,11 +6472,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
) -> Type<'db> {
let target = target.into();
if let Ok(place_expr) = PlaceExpr::try_from(target) {
self.narrow_place_with_applicable_constraints(&place_expr, target_ty, constraint_keys)
} else {
target_ty
}
self.narrow_place_with_applicable_constraints(
&PlaceExpr::from(target),
target_ty,
constraint_keys,
)
}
/// Infer the type of a [`ast::ExprAttribute`] expression, assuming a load context.
@ -6512,13 +6494,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let mut constraint_keys = vec![];
let mut assigned_type = None;
if let Ok(place_expr) = PlaceExpr::try_from(attribute) {
let (resolved, keys) =
self.infer_place_load(&place_expr, ast::ExprRef::Attribute(attribute));
constraint_keys.extend(keys);
if let Place::Type(ty, Boundness::Bound) = resolved.place {
assigned_type = Some(ty);
}
let (resolved, keys) = self.infer_place_load(
&PlaceExpr::from(attribute),
ast::ExprRef::Attribute(attribute),
);
constraint_keys.extend(keys);
if let Place::Type(ty, Boundness::Bound) = resolved.place {
assigned_type = Some(ty);
}
let resolved_type = value_type
@ -8043,17 +8025,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// If `value` is a valid reference, we attempt type narrowing by assignment.
if !value_ty.is_unknown() {
if let Ok(expr) = PlaceExpr::try_from(subscript) {
let (place, keys) =
self.infer_place_load(&expr, ast::ExprRef::Subscript(subscript));
constraint_keys.extend(keys);
if let Place::Type(ty, Boundness::Bound) = place.place {
// Even if we can obtain the subscript type based on the assignments, we still perform default type inference
// (to store the expression type and to report errors).
let slice_ty = self.infer_expression(slice);
self.infer_subscript_expression_types(value, value_ty, slice_ty);
return ty;
}
let (place, keys) = self.infer_place_load(
&PlaceExpr::from(subscript),
ast::ExprRef::Subscript(subscript),
);
constraint_keys.extend(keys);
if let Place::Type(ty, Boundness::Bound) = place.place {
// Even if we can obtain the subscript type based on the assignments, we still perform default type inference
// (to store the expression type and to report errors).
let slice_ty = self.infer_expression(slice);
self.infer_subscript_expression_types(value, value_ty, slice_ty);
return ty;
}
}
@ -8560,7 +8542,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
pub(super) fn finish(mut self) -> TypeInference<'db> {
self.infer_region();
self.types.diagnostics = self.context.finish();
self.types.set_diagnostics(self.context.finish());
self.types.shrink_to_fit();
self.types
}

View file

@ -248,12 +248,14 @@ fn negate_if<'db>(constraints: &mut NarrowingConstraints<'db>, db: &'db dyn Db,
}
fn place_expr(expr: &ast::Expr) -> Option<PlaceExpr> {
match expr {
ast::Expr::Name(name) => Some(PlaceExpr::name(name.id.clone())),
ast::Expr::Attribute(attr) => PlaceExpr::try_from(attr).ok(),
ast::Expr::Subscript(subscript) => PlaceExpr::try_from(subscript).ok(),
ast::Expr::Named(named) => PlaceExpr::try_from(named.target.as_ref()).ok(),
_ => None,
let place_expr = match expr {
ast::Expr::Named(named) => PlaceExpr::from(named.target.as_ref()),
_ => PlaceExpr::from(expr),
};
if place_expr.is_valid() {
Some(place_expr)
} else {
None
}
}

View file

@ -10,18 +10,34 @@ use crate::Db;
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
use crate::semantic_index::place::ScopeId;
use crate::types::tuple::{FixedLengthTupleSpec, TupleSpec, TupleType};
use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types, todo_type};
use crate::types::{Type, infer_expression_types, todo_type};
use crate::unpack::{UnpackKind, UnpackValue};
use super::context::InferContext;
use super::diagnostic::INVALID_ASSIGNMENT;
use super::infer::TypeInference;
use super::{KnownClass, UnionType};
/// Unpacks the value expression type to their respective targets.
pub(crate) struct Unpacker<'db, 'ast> {
context: InferContext<'db, 'ast>,
/// The type inference result for the unpacked value expression.
///
/// This does not include the target types, which are stored in `targets` field.
value_types: TypeInference<'db>,
/// The scope in which the target expressions belongs to.
target_scope: ScopeId<'db>,
/// The scope in which the value expression belongs to.
///
/// For more information, see [`value_scope`].
///
/// [`value_scope`]: crate::unpack::Unpack::value_scope
value_scope: ScopeId<'db>,
/// The inferred types of the target expressions involved in the unpacking.
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
}
@ -34,6 +50,7 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
) -> Self {
Self {
context: InferContext::new(db, target_scope, module),
value_types: TypeInference::empty(value_scope),
targets: FxHashMap::default(),
target_scope,
value_scope,
@ -55,11 +72,20 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
"Unpacking target must be a list or tuple expression"
);
let value_type = infer_expression_types(self.db(), value.expression()).expression_type(
value.scoped_expression_id(self.db(), self.value_scope, self.module()),
);
let value_types_result = infer_expression_types(self.db(), value.expression());
let value_type = match value.kind() {
// Make sure the inference result and diagnostics are stored in the unpacker so that it's
// propagated outside the unpacker via the `UnpackResult`.
self.value_types.extend(value_types_result);
self.context.extend(value_types_result.diagnostics());
let mut value_type = value_types_result.expression_type(value.scoped_expression_id(
self.db(),
self.value_scope,
self.module(),
));
value_type = match value.kind() {
UnpackKind::Assign => {
if self.context.in_stub()
&& value
@ -335,18 +361,27 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
pub(crate) fn finish(mut self) -> UnpackResult<'db> {
self.targets.shrink_to_fit();
self.value_types.set_diagnostics(self.context.finish());
self.value_types.shrink_to_fit();
UnpackResult {
diagnostics: self.context.finish(),
types: self.value_types,
targets: self.targets,
cycle_fallback_type: None,
}
}
}
#[derive(Debug, Default, PartialEq, Eq, salsa::Update)]
#[derive(Debug, PartialEq, Eq, salsa::Update)]
pub(crate) struct UnpackResult<'db> {
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
diagnostics: TypeCheckDiagnostics,
/// The type inference result for the unpacked value expression.
///
/// This does not include the target types, which are stored in `targets` field. This does
/// include all the diagnostics that were collected during the unpacking process.
types: TypeInference<'db>,
/// The fallback type for missing expressions.
///
@ -377,15 +412,14 @@ impl<'db> UnpackResult<'db> {
.or(self.cycle_fallback_type)
}
/// Returns the diagnostics in this unpacking assignment.
pub(crate) fn diagnostics(&self) -> &TypeCheckDiagnostics {
&self.diagnostics
pub(super) fn types(&self) -> &TypeInference<'db> {
&self.types
}
pub(crate) fn cycle_fallback(cycle_fallback_type: Type<'db>) -> Self {
pub(crate) fn cycle_fallback(scope_id: ScopeId<'db>, cycle_fallback_type: Type<'db>) -> Self {
Self {
targets: FxHashMap::default(),
diagnostics: TypeCheckDiagnostics::default(),
types: TypeInference::empty(scope_id),
cycle_fallback_type: Some(cycle_fallback_type),
}
}