[red-knot] handle all syntax without panic (#12499)

Extend red-knot type inference to cover all syntax, so that inferring
types for a scope gives all expressions a type. This means we can run
the red-knot semantic lint on all Python code without panics. It also
means we can infer types for `builtins.pyi` without panics.

To keep things simple, this PR intentionally doesn't add any new type
inference capabilities: the expanded coverage is all achieved with
`Type::Unknown`. But this puts the skeleton in place for adding better
inference of all these language features.

I also had to add basic Salsa cycle recovery (with just `Type::Unknown`
for now), because some `builtins.pyi` definitions are cyclic.

To test this, I added a comprehensive corpus of test snippets sourced
from Cinder under [MIT
license](https://github.com/facebookincubator/cinder/blob/cinder/3.10/cinderx/LICENSE),
which matches Ruff's license. I also added to this corpus some
additional snippets for newer language features: all the
`27_func_generic_*` and `73_class_generic_*` files, as well as
`20_lambda_default_arg.py`, and added a test which runs semantic-lint
over all these files. (The test doesn't assert the test-corpus files are
lint-free; just that they are able to lint without a panic.)
This commit is contained in:
Carl Meyer 2024-07-25 17:38:08 -07:00 committed by GitHub
parent 7571da8778
commit 2d3914296d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
276 changed files with 2002 additions and 92 deletions

View file

@ -214,30 +214,38 @@ impl<'db> SemanticIndexBuilder<'db> {
fn with_type_params(
&mut self,
with_params: &WithTypeParams,
with_scope: NodeWithScopeRef,
type_params: Option<&'db ast::TypeParams>,
nested: impl FnOnce(&mut Self) -> FileScopeId,
) -> FileScopeId {
let type_params = with_params.type_parameters();
if let Some(type_params) = type_params {
let with_scope = match with_params {
WithTypeParams::ClassDef { node, .. } => {
NodeWithScopeRef::ClassTypeParameters(node)
}
WithTypeParams::FunctionDef { node, .. } => {
NodeWithScopeRef::FunctionTypeParameters(node)
}
};
self.push_scope(with_scope);
for type_param in &type_params.type_params {
let name = match type_param {
ast::TypeParam::TypeVar(ast::TypeParamTypeVar { name, .. }) => name,
ast::TypeParam::ParamSpec(ast::TypeParamParamSpec { name, .. }) => name,
ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple { name, .. }) => name,
let (name, bound, default) = match type_param {
ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
range: _,
name,
bound,
default,
}) => (name, bound, default),
ast::TypeParam::ParamSpec(ast::TypeParamParamSpec {
name, default, ..
}) => (name, &None, default),
ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple {
name,
default,
..
}) => (name, &None, default),
};
// TODO create Definition for typevars
self.add_or_update_symbol(name.id.clone(), SymbolFlags::IS_DEFINED);
if let Some(bound) = bound {
self.visit_expr(bound);
}
if let Some(default) = default {
self.visit_expr(default);
}
}
}
@ -318,7 +326,8 @@ where
self.add_definition(symbol, function_def);
self.with_type_params(
&WithTypeParams::FunctionDef { node: function_def },
NodeWithScopeRef::FunctionTypeParameters(function_def),
function_def.type_params.as_deref(),
|builder| {
builder.visit_parameters(&function_def.parameters);
for expr in &function_def.returns {
@ -340,16 +349,20 @@ where
self.add_or_update_symbol(class.name.id.clone(), SymbolFlags::IS_DEFINED);
self.add_definition(symbol, class);
self.with_type_params(&WithTypeParams::ClassDef { node: class }, |builder| {
if let Some(arguments) = &class.arguments {
builder.visit_arguments(arguments);
}
self.with_type_params(
NodeWithScopeRef::ClassTypeParameters(class),
class.type_params.as_deref(),
|builder| {
if let Some(arguments) = &class.arguments {
builder.visit_arguments(arguments);
}
builder.push_scope(NodeWithScopeRef::Class(class));
builder.visit_body(&class.body);
builder.push_scope(NodeWithScopeRef::Class(class));
builder.visit_body(&class.body);
builder.pop_scope()
});
builder.pop_scope()
},
);
}
ast::Stmt::Import(node) => {
for alias in &node.names {
@ -390,18 +403,12 @@ where
debug_assert!(self.current_assignment.is_none());
// TODO deferred annotation visiting
self.visit_expr(&node.annotation);
match &node.value {
Some(value) => {
self.visit_expr(value);
self.current_assignment = Some(node.into());
self.visit_expr(&node.target);
self.current_assignment = None;
}
None => {
// TODO annotation-only assignments
self.visit_expr(&node.target);
}
if let Some(value) = &node.value {
self.visit_expr(value);
}
self.current_assignment = Some(node.into());
self.visit_expr(&node.target);
self.current_assignment = None;
}
ast::Stmt::If(node) => {
self.visit_expr(&node.test);
@ -514,6 +521,14 @@ where
self.current_assignment = None;
self.visit_expr(&node.value);
}
ast::Expr::Lambda(lambda) => {
if let Some(parameters) = &lambda.parameters {
self.visit_parameters(parameters);
}
self.push_scope(NodeWithScopeRef::Lambda(lambda));
self.visit_expr(lambda.body.as_ref());
self.pop_scope();
}
ast::Expr::If(ast::ExprIf {
body, test, orelse, ..
}) => {
@ -535,20 +550,6 @@ where
}
}
enum WithTypeParams<'node> {
ClassDef { node: &'node ast::StmtClassDef },
FunctionDef { node: &'node ast::StmtFunctionDef },
}
impl<'node> WithTypeParams<'node> {
fn type_parameters(&self) -> Option<&'node ast::TypeParams> {
match self {
WithTypeParams::ClassDef { node, .. } => node.type_params.as_deref(),
WithTypeParams::FunctionDef { node, .. } => node.type_params.as_deref(),
}
}
}
#[derive(Copy, Clone, Debug)]
enum CurrentAssignment<'a> {
Assign(&'a ast::StmtAssign),

View file

@ -126,6 +126,7 @@ impl<'db> ScopeId<'db> {
}
NodeWithScopeKind::Function(function)
| NodeWithScopeKind::FunctionTypeParameters(function) => function.name.as_str(),
NodeWithScopeKind::Lambda(_) => "<lambda>",
}
}
}
@ -296,6 +297,7 @@ pub(crate) enum NodeWithScopeRef<'a> {
Module,
Class(&'a ast::StmtClassDef),
Function(&'a ast::StmtFunctionDef),
Lambda(&'a ast::ExprLambda),
FunctionTypeParameters(&'a ast::StmtFunctionDef),
ClassTypeParameters(&'a ast::StmtClassDef),
}
@ -315,11 +317,14 @@ impl NodeWithScopeRef<'_> {
NodeWithScopeRef::Function(function) => {
NodeWithScopeKind::Function(AstNodeRef::new(module, function))
}
NodeWithScopeRef::Lambda(lambda) => {
NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda))
}
NodeWithScopeRef::FunctionTypeParameters(function) => {
NodeWithScopeKind::FunctionTypeParameters(AstNodeRef::new(module, function))
}
NodeWithScopeRef::ClassTypeParameters(class) => {
NodeWithScopeKind::Class(AstNodeRef::new(module, class))
NodeWithScopeKind::ClassTypeParameters(AstNodeRef::new(module, class))
}
}
}
@ -329,6 +334,7 @@ impl NodeWithScopeRef<'_> {
NodeWithScopeRef::Module => ScopeKind::Module,
NodeWithScopeRef::Class(_) => ScopeKind::Class,
NodeWithScopeRef::Function(_) => ScopeKind::Function,
NodeWithScopeRef::Lambda(_) => ScopeKind::Function,
NodeWithScopeRef::FunctionTypeParameters(_)
| NodeWithScopeRef::ClassTypeParameters(_) => ScopeKind::Annotation,
}
@ -341,6 +347,9 @@ impl NodeWithScopeRef<'_> {
NodeWithScopeRef::Function(function) => {
NodeWithScopeKey::Function(NodeKey::from_node(function))
}
NodeWithScopeRef::Lambda(lambda) => {
NodeWithScopeKey::Lambda(NodeKey::from_node(lambda))
}
NodeWithScopeRef::FunctionTypeParameters(function) => {
NodeWithScopeKey::FunctionTypeParameters(NodeKey::from_node(function))
}
@ -359,6 +368,7 @@ pub enum NodeWithScopeKind {
ClassTypeParameters(AstNodeRef<ast::StmtClassDef>),
Function(AstNodeRef<ast::StmtFunctionDef>),
FunctionTypeParameters(AstNodeRef<ast::StmtFunctionDef>),
Lambda(AstNodeRef<ast::ExprLambda>),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
@ -368,4 +378,5 @@ pub(crate) enum NodeWithScopeKey {
ClassTypeParameters(NodeKey),
Function(NodeKey),
FunctionTypeParameters(NodeKey),
Lambda(NodeKey),
}

View file

@ -57,9 +57,21 @@ pub(crate) fn infer_scope_types<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Ty
TypeInferenceBuilder::new(db, InferenceRegion::Scope(scope), index).finish()
}
/// Cycle recovery for [`infer_definition_types`]: for now, just [`Type::Unknown`]
/// TODO fixpoint iteration
fn infer_definition_types_cycle_recovery<'db>(
_db: &'db dyn Db,
_cycle: &salsa::Cycle,
input: Definition<'db>,
) -> TypeInference<'db> {
let mut inference = TypeInference::default();
inference.definitions.insert(input, Type::Unknown);
inference
}
/// Infer all types for a [`Definition`] (including sub-expressions).
/// Use when resolving a symbol name use or public type of a symbol.
#[salsa::tracked(return_ref)]
#[salsa::tracked(return_ref, recovery_fn=infer_definition_types_cycle_recovery)]
pub(crate) fn infer_definition_types<'db>(
db: &'db dyn Db,
definition: Definition<'db>,
@ -229,6 +241,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_module(parsed.syntax());
}
NodeWithScopeKind::Function(function) => self.infer_function_body(function.node()),
NodeWithScopeKind::Lambda(lambda) => self.infer_lambda_body(lambda.node()),
NodeWithScopeKind::Class(class) => self.infer_class_body(class.node()),
NodeWithScopeKind::ClassTypeParameters(class) => {
self.infer_class_type_params(class.node());
@ -276,8 +289,15 @@ impl<'db> TypeInferenceBuilder<'db> {
}
fn infer_class_type_params(&mut self, class: &ast::StmtClassDef) {
if let Some(type_params) = class.type_params.as_deref() {
self.infer_type_parameters(type_params);
let type_params = class
.type_params
.as_deref()
.expect("class type params scope without type params");
self.infer_type_parameters(type_params);
if let Some(arguments) = class.arguments.as_deref() {
self.infer_arguments(arguments);
}
}
@ -286,9 +306,12 @@ impl<'db> TypeInferenceBuilder<'db> {
}
fn infer_function_type_params(&mut self, function: &ast::StmtFunctionDef) {
if let Some(type_params) = function.type_params.as_deref() {
self.infer_type_parameters(type_params);
}
let Some(type_params) = function.type_params.as_deref() else {
panic!("function type params scope without type params");
};
self.infer_type_parameters(type_params);
self.infer_parameters(&function.parameters);
self.infer_optional_expression(function.returns.as_deref());
}
fn infer_function_body(&mut self, function: &ast::StmtFunctionDef) {
@ -309,16 +332,31 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_expression(value);
}
ast::Stmt::If(if_statement) => self.infer_if_statement(if_statement),
ast::Stmt::Try(try_statement) => self.infer_try_statement(try_statement),
ast::Stmt::With(with_statement) => self.infer_with_statement(with_statement),
ast::Stmt::Match(match_statement) => self.infer_match_statement(match_statement),
ast::Stmt::Assign(assign) => self.infer_assignment_statement(assign),
ast::Stmt::AnnAssign(assign) => self.infer_annotated_assignment_statement(assign),
ast::Stmt::AugAssign(aug_assign) => {
self.infer_augmented_assignment_statement(aug_assign);
}
ast::Stmt::TypeAlias(type_statement) => self.infer_type_alias_statement(type_statement),
ast::Stmt::For(for_statement) => self.infer_for_statement(for_statement),
ast::Stmt::While(while_statement) => self.infer_while_statement(while_statement),
ast::Stmt::Import(import) => self.infer_import_statement(import),
ast::Stmt::ImportFrom(import) => self.infer_import_from_statement(import),
ast::Stmt::Assert(assert_statement) => self.infer_assert_statement(assert_statement),
ast::Stmt::Raise(raise) => self.infer_raise_statement(raise),
ast::Stmt::Return(ret) => self.infer_return_statement(ret),
ast::Stmt::Break(_) | ast::Stmt::Continue(_) | ast::Stmt::Pass(_) => {
ast::Stmt::Delete(delete) => self.infer_delete_statement(delete),
ast::Stmt::Break(_)
| ast::Stmt::Continue(_)
| ast::Stmt::Pass(_)
| ast::Stmt::IpyEscapeCommand(_)
| ast::Stmt::Global(_)
| ast::Stmt::Nonlocal(_) => {
// No-op
}
_ => {}
}
}
@ -341,8 +379,8 @@ impl<'db> TypeInferenceBuilder<'db> {
range: _,
is_async: _,
name,
type_params: _,
parameters: _,
type_params,
parameters,
returns,
body: _,
decorator_list,
@ -353,10 +391,10 @@ impl<'db> TypeInferenceBuilder<'db> {
.map(|decorator| self.infer_decorator(decorator))
.collect();
// TODO: Infer parameters
if let Some(return_expr) = returns {
self.infer_expression(return_expr);
// If there are type params, parameters and returns are evaluated in that scope.
if type_params.is_none() {
self.infer_parameters(parameters);
self.infer_optional_expression(returns.as_deref());
}
let function_ty =
@ -365,6 +403,46 @@ impl<'db> TypeInferenceBuilder<'db> {
self.types.definitions.insert(definition, function_ty);
}
fn infer_parameters(&mut self, parameters: &ast::Parameters) {
let ast::Parameters {
range: _,
posonlyargs: _,
args: _,
vararg,
kwonlyargs: _,
kwarg,
} = parameters;
for param_with_default in parameters.iter_non_variadic_params() {
self.infer_parameter_with_default(param_with_default);
}
if let Some(vararg) = vararg {
self.infer_parameter(vararg);
}
if let Some(kwarg) = kwarg {
self.infer_parameter(kwarg);
}
}
fn infer_parameter_with_default(&mut self, parameter_with_default: &ast::ParameterWithDefault) {
let ast::ParameterWithDefault {
range: _,
parameter,
default,
} = parameter_with_default;
self.infer_parameter(parameter);
self.infer_optional_expression(default.as_deref());
}
fn infer_parameter(&mut self, parameter: &ast::Parameter) {
let ast::Parameter {
range: _,
name: _,
annotation,
} = parameter;
self.infer_optional_expression(annotation.as_deref());
}
fn infer_class_definition_statement(&mut self, class: &ast::StmtClassDef) {
self.infer_definition(class);
}
@ -383,6 +461,8 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_decorator(decorator);
}
// TODO if there are type params, the bases should be inferred inside that scope (only)
let bases = arguments
.as_deref()
.map(|arguments| self.infer_arguments(arguments))
@ -416,27 +496,142 @@ impl<'db> TypeInferenceBuilder<'db> {
body,
} = clause;
if let Some(test) = &test {
self.infer_expression(test);
}
self.infer_optional_expression(test.as_ref());
self.infer_body(body);
}
}
fn infer_try_statement(&mut self, try_statement: &ast::StmtTry) {
let ast::StmtTry {
range: _,
body,
handlers,
orelse,
finalbody,
is_star: _,
} = try_statement;
self.infer_body(body);
for handler in handlers {
let ast::ExceptHandler::ExceptHandler(handler) = handler;
self.infer_optional_expression(handler.type_.as_deref());
self.infer_body(&handler.body);
}
self.infer_body(orelse);
self.infer_body(finalbody);
}
fn infer_with_statement(&mut self, with_statement: &ast::StmtWith) {
let ast::StmtWith {
range: _,
is_async: _,
items,
body,
} = with_statement;
for item in items {
self.infer_expression(&item.context_expr);
self.infer_optional_expression(item.optional_vars.as_deref());
}
self.infer_body(body);
}
fn infer_match_statement(&mut self, match_statement: &ast::StmtMatch) {
let ast::StmtMatch {
range: _,
subject,
cases,
} = match_statement;
self.infer_expression(subject);
for case in cases {
let ast::MatchCase {
range: _,
body,
pattern,
guard,
} = case;
// TODO infer case patterns; they aren't normal expressions
self.infer_match_pattern(pattern);
self.infer_optional_expression(guard.as_deref());
self.infer_body(body);
}
}
fn infer_match_pattern(&mut self, pattern: &ast::Pattern) {
match pattern {
ast::Pattern::MatchValue(match_value) => {
self.infer_expression(&match_value.value);
}
ast::Pattern::MatchSequence(match_sequence) => {
for pattern in &match_sequence.patterns {
self.infer_match_pattern(pattern);
}
}
ast::Pattern::MatchMapping(match_mapping) => {
let ast::PatternMatchMapping {
range: _,
keys,
patterns,
rest: _,
} = match_mapping;
for key in keys {
self.infer_expression(key);
}
for pattern in patterns {
self.infer_match_pattern(pattern);
}
}
ast::Pattern::MatchClass(match_class) => {
let ast::PatternMatchClass {
range: _,
cls,
arguments,
} = match_class;
for pattern in &arguments.patterns {
self.infer_match_pattern(pattern);
}
for keyword in &arguments.keywords {
self.infer_match_pattern(&keyword.pattern);
}
self.infer_expression(cls);
}
ast::Pattern::MatchAs(match_as) => {
if let Some(pattern) = &match_as.pattern {
self.infer_match_pattern(pattern);
}
}
ast::Pattern::MatchOr(match_or) => {
for pattern in &match_or.patterns {
self.infer_match_pattern(pattern);
}
}
ast::Pattern::MatchStar(_) | ast::Pattern::MatchSingleton(_) => {}
};
}
fn infer_assignment_statement(&mut self, assignment: &ast::StmtAssign) {
let ast::StmtAssign {
range: _,
targets,
value: _,
value,
} = assignment;
// TODO remove once we infer definitions in unpacking assignment, since that infers the RHS
// too, and uses the `infer_expression_types` query to do it
self.infer_expression(value);
for target in targets {
match target {
ast::Expr::Name(name) => {
self.infer_definition(name);
}
_ => todo!("support unpacking assignment"),
_ => {
// TODO infer definitions in unpacking assignment
self.infer_expression(target);
}
}
}
}
@ -456,7 +651,12 @@ impl<'db> TypeInferenceBuilder<'db> {
}
fn infer_annotated_assignment_statement(&mut self, assignment: &ast::StmtAnnAssign) {
self.infer_definition(assignment);
if let ast::Expr::Name(_) = assignment.target.as_ref() {
self.infer_definition(assignment);
} else {
// currently we don't consider assignments to non-Names to be Definitions
self.infer_annotated_assignment(assignment);
}
}
fn infer_annotated_assignment_definition(
@ -464,6 +664,11 @@ impl<'db> TypeInferenceBuilder<'db> {
assignment: &ast::StmtAnnAssign,
definition: Definition<'db>,
) {
let ty = self.infer_annotated_assignment(assignment);
self.types.definitions.insert(definition, ty);
}
fn infer_annotated_assignment(&mut self, assignment: &ast::StmtAnnAssign) -> Type<'db> {
let ast::StmtAnnAssign {
range: _,
target,
@ -472,15 +677,39 @@ impl<'db> TypeInferenceBuilder<'db> {
simple: _,
} = assignment;
if let Some(value) = value {
let _ = self.infer_expression(value);
}
self.infer_optional_expression(value.as_deref());
let annotation_ty = self.infer_expression(annotation);
self.infer_expression(target);
self.types.definitions.insert(definition, annotation_ty);
annotation_ty
}
fn infer_augmented_assignment_statement(&mut self, assignment: &ast::StmtAugAssign) {
// TODO this should be a Definition
let ast::StmtAugAssign {
range: _,
target,
op: _,
value,
} = assignment;
self.infer_expression(target);
self.infer_expression(value);
}
fn infer_type_alias_statement(&mut self, type_alias_statement: &ast::StmtTypeAlias) {
let ast::StmtTypeAlias {
range: _,
name,
type_params,
value,
} = type_alias_statement;
self.infer_expression(value);
self.infer_expression(name);
if let Some(type_params) = type_params {
self.infer_type_parameters(type_params);
}
}
fn infer_for_statement(&mut self, for_statement: &ast::StmtFor) {
@ -499,6 +728,19 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_body(orelse);
}
fn infer_while_statement(&mut self, while_statement: &ast::StmtWhile) {
let ast::StmtWhile {
range: _,
test,
body,
orelse,
} = while_statement;
self.infer_expression(test);
self.infer_body(body);
self.infer_body(orelse);
}
fn infer_import_statement(&mut self, import: &ast::StmtImport) {
let ast::StmtImport { range: _, names } = import;
@ -531,6 +773,27 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
fn infer_assert_statement(&mut self, assert: &ast::StmtAssert) {
let ast::StmtAssert {
range: _,
test,
msg,
} = assert;
self.infer_expression(test);
self.infer_optional_expression(msg.as_deref());
}
fn infer_raise_statement(&mut self, raise: &ast::StmtRaise) {
let ast::StmtRaise {
range: _,
exc,
cause,
} = raise;
self.infer_optional_expression(exc.as_deref());
self.infer_optional_expression(cause.as_deref());
}
fn infer_import_from_definition(
&mut self,
import_from: &ast::StmtImportFrom,
@ -538,8 +801,12 @@ impl<'db> TypeInferenceBuilder<'db> {
definition: Definition<'db>,
) {
let ast::StmtImportFrom { module, .. } = import_from;
let module_ty =
self.module_ty_from_name(module.as_ref().expect("Support relative imports"));
let module_ty = if let Some(module) = module {
self.module_ty_from_name(module)
} else {
// TODO support relative imports
Type::Unknown
};
let ast::Alias {
range: _,
@ -553,8 +820,13 @@ impl<'db> TypeInferenceBuilder<'db> {
}
fn infer_return_statement(&mut self, ret: &ast::StmtReturn) {
if let Some(value) = &ret.value {
self.infer_expression(value);
self.infer_optional_expression(ret.value.as_deref());
}
fn infer_delete_statement(&mut self, delete: &ast::StmtDelete) {
let ast::StmtDelete { range: _, targets } = delete;
for target in targets {
self.infer_expression(target);
}
}
@ -597,15 +869,42 @@ impl<'db> TypeInferenceBuilder<'db> {
types
}
fn infer_optional_expression(&mut self, expression: Option<&ast::Expr>) -> Option<Type<'db>> {
expression.map(|expr| self.infer_expression(expr))
}
fn infer_expression(&mut self, expression: &ast::Expr) -> Type<'db> {
let ty = match expression {
ast::Expr::NoneLiteral(ast::ExprNoneLiteral { range: _ }) => Type::None,
ast::Expr::NumberLiteral(literal) => self.infer_number_literal_expression(literal),
ast::Expr::BooleanLiteral(literal) => self.infer_boolean_literal_expression(literal),
ast::Expr::StringLiteral(literal) => self.infer_string_literal_expression(literal),
ast::Expr::FString(fstring) => self.infer_fstring_expression(fstring),
ast::Expr::EllipsisLiteral(literal) => self.infer_ellipsis_literal_expression(literal),
ast::Expr::Tuple(tuple) => self.infer_tuple_expression(tuple),
ast::Expr::List(list) => self.infer_list_expression(list),
ast::Expr::Set(set) => self.infer_set_expression(set),
ast::Expr::Dict(dict) => self.infer_dict_expression(dict),
ast::Expr::Generator(generator) => self.infer_generator_expression(generator),
ast::Expr::ListComp(listcomp) => self.infer_list_comprehension_expression(listcomp),
ast::Expr::DictComp(dictcomp) => self.infer_dict_comprehension_expression(dictcomp),
ast::Expr::SetComp(setcomp) => self.infer_set_comprehension_expression(setcomp),
ast::Expr::Name(name) => self.infer_name_expression(name),
ast::Expr::Attribute(attribute) => self.infer_attribute_expression(attribute),
ast::Expr::UnaryOp(unary_op) => self.infer_unary_expression(unary_op),
ast::Expr::BinOp(binary) => self.infer_binary_expression(binary),
ast::Expr::BoolOp(bool_op) => self.infer_boolean_expression(bool_op),
ast::Expr::Compare(compare) => self.infer_compare_expression(compare),
ast::Expr::Subscript(subscript) => self.infer_subscript_expression(subscript),
ast::Expr::Slice(slice) => self.infer_slice_expression(slice),
ast::Expr::Named(named) => self.infer_named_expression(named),
ast::Expr::If(if_expression) => self.infer_if_expression(if_expression),
ast::Expr::Lambda(lambda_expression) => self.infer_lambda_expression(lambda_expression),
ast::Expr::Call(call_expression) => self.infer_call_expression(call_expression),
ast::Expr::Starred(starred) => self.infer_starred_expression(starred),
ast::Expr::Yield(yield_expression) => self.infer_yield_expression(yield_expression),
ast::Expr::YieldFrom(yield_from) => self.infer_yield_from_expression(yield_from),
ast::Expr::Await(await_expression) => self.infer_await_expression(await_expression),
_ => todo!("expression type resolution for {:?}", expression),
};
@ -630,6 +929,208 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
#[allow(clippy::unused_self)]
fn infer_boolean_literal_expression(
&mut self,
_literal: &ast::ExprBooleanLiteral,
) -> Type<'db> {
// TODO builtins.bool and boolean Literal types
Type::Unknown
}
#[allow(clippy::unused_self)]
fn infer_string_literal_expression(&mut self, _literal: &ast::ExprStringLiteral) -> Type<'db> {
// TODO Literal[str] or builtins.str
Type::Unknown
}
fn infer_fstring_expression(&mut self, fstring: &ast::ExprFString) -> Type<'db> {
let ast::ExprFString { range: _, value } = fstring;
for part in value {
match part {
ast::FStringPart::Literal(_) => {
// TODO string literal type
}
ast::FStringPart::FString(fstring) => {
let ast::FString {
range: _,
elements,
flags: _,
} = fstring;
for element in elements {
match element {
ast::FStringElement::Literal(_) => {
// TODO string literal type
}
ast::FStringElement::Expression(expr_element) => {
let ast::FStringExpressionElement {
range: _,
expression,
debug_text: _,
conversion: _,
format_spec: _,
} = expr_element;
self.infer_expression(expression);
}
}
}
}
}
}
// TODO str type
Type::Unknown
}
#[allow(clippy::unused_self)]
fn infer_ellipsis_literal_expression(
&mut self,
_literal: &ast::ExprEllipsisLiteral,
) -> Type<'db> {
// TODO builtins.Ellipsis
Type::Unknown
}
fn infer_tuple_expression(&mut self, tuple: &ast::ExprTuple) -> Type<'db> {
let ast::ExprTuple {
range: _,
elts,
ctx: _,
parenthesized: _,
} = tuple;
for elt in elts {
self.infer_expression(elt);
}
// TODO tuple type
Type::Unknown
}
fn infer_list_expression(&mut self, list: &ast::ExprList) -> Type<'db> {
let ast::ExprList {
range: _,
elts,
ctx: _,
} = list;
for elt in elts {
self.infer_expression(elt);
}
// TODO list type
Type::Unknown
}
fn infer_set_expression(&mut self, set: &ast::ExprSet) -> Type<'db> {
let ast::ExprSet { range: _, elts } = set;
for elt in elts {
self.infer_expression(elt);
}
// TODO set type
Type::Unknown
}
fn infer_dict_expression(&mut self, dict: &ast::ExprDict) -> Type<'db> {
let ast::ExprDict { range: _, items } = dict;
for item in items {
self.infer_optional_expression(item.key.as_ref());
self.infer_expression(&item.value);
}
// TODO dict type
Type::Unknown
}
fn infer_generator_expression(&mut self, generator: &ast::ExprGenerator) -> Type<'db> {
let ast::ExprGenerator {
range: _,
elt,
generators,
parenthesized: _,
} = generator;
self.infer_expression(elt);
for generator in generators {
self.infer_comprehension(generator);
}
// TODO generator type
Type::Unknown
}
fn infer_list_comprehension_expression(&mut self, listcomp: &ast::ExprListComp) -> Type<'db> {
let ast::ExprListComp {
range: _,
elt,
generators,
} = listcomp;
self.infer_expression(elt);
for generator in generators {
self.infer_comprehension(generator);
}
// TODO list type
Type::Unknown
}
fn infer_dict_comprehension_expression(&mut self, dictcomp: &ast::ExprDictComp) -> Type<'db> {
let ast::ExprDictComp {
range: _,
key,
value,
generators,
} = dictcomp;
self.infer_expression(key);
self.infer_expression(value);
for generator in generators {
self.infer_comprehension(generator);
}
// TODO dict type
Type::Unknown
}
fn infer_set_comprehension_expression(&mut self, setcomp: &ast::ExprSetComp) -> Type<'db> {
let ast::ExprSetComp {
range: _,
elt,
generators,
} = setcomp;
self.infer_expression(elt);
for generator in generators {
self.infer_comprehension(generator);
}
// TODO set type
Type::Unknown
}
fn infer_comprehension(&mut self, comprehension: &ast::Comprehension) -> Type<'db> {
let ast::Comprehension {
range: _,
target,
iter,
ifs,
is_async: _,
} = comprehension;
self.infer_expression(target);
self.infer_expression(iter);
for if_clause in ifs {
self.infer_expression(if_clause);
}
// TODO comprehension type
Type::Unknown
}
fn infer_named_expression(&mut self, named: &ast::ExprNamed) -> Type<'db> {
let definition = self.index.definition(named);
let result = infer_definition_types(self.db, definition);
@ -678,6 +1179,79 @@ impl<'db> TypeInferenceBuilder<'db> {
Type::Union(union)
}
fn infer_lambda_body(&mut self, lambda_expression: &ast::ExprLambda) {
self.infer_expression(&lambda_expression.body);
}
fn infer_lambda_expression(&mut self, lambda_expression: &ast::ExprLambda) -> Type<'db> {
let ast::ExprLambda {
range: _,
parameters,
body: _,
} = lambda_expression;
if let Some(parameters) = parameters {
self.infer_parameters(parameters);
}
// TODO function type
Type::Unknown
}
fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> {
let ast::ExprCall {
range: _,
func,
arguments,
} = call_expression;
self.infer_arguments(arguments);
self.infer_expression(func);
// TODO resolve to return type of `func`, if its a callable type
Type::Unknown
}
fn infer_starred_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> {
let ast::ExprStarred {
range: _,
value,
ctx: _,
} = starred;
self.infer_expression(value);
// TODO
Type::Unknown
}
fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> {
let ast::ExprYield { range: _, value } = yield_expression;
self.infer_optional_expression(value.as_deref());
// TODO awaitable type
Type::Unknown
}
fn infer_yield_from_expression(&mut self, yield_from: &ast::ExprYieldFrom) -> Type<'db> {
let ast::ExprYieldFrom { range: _, value } = yield_from;
self.infer_expression(value);
// TODO get type from awaitable
Type::Unknown
}
fn infer_await_expression(&mut self, await_expression: &ast::ExprAwait) -> Type<'db> {
let ast::ExprAwait { range: _, value } = await_expression;
self.infer_expression(value);
// TODO awaitable type
Type::Unknown
}
fn infer_name_expression(&mut self, name: &ast::ExprName) -> Type<'db> {
let ast::ExprName { range: _, id, ctx } = name;
@ -738,6 +1312,19 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
fn infer_unary_expression(&mut self, unary: &ast::ExprUnaryOp) -> Type<'db> {
let ast::ExprUnaryOp {
range: _,
op: _,
operand,
} = unary;
self.infer_expression(operand);
// TODO unary op types
Type::Unknown
}
fn infer_binary_expression(&mut self, binary: &ast::ExprBinOp) -> Type<'db> {
let ast::ExprBinOp {
left,
@ -781,18 +1368,113 @@ impl<'db> TypeInferenceBuilder<'db> {
.map(Type::IntLiteral)
// TODO division by zero error
.unwrap_or(Type::Unknown),
_ => todo!("complete binop op support for IntLiteral"),
_ => Type::Unknown, // TODO
}
}
_ => todo!("complete binop right_ty support for IntLiteral"),
_ => Type::Unknown, // TODO
}
}
_ => todo!("complete binop support"),
_ => Type::Unknown, // TODO
}
}
fn infer_type_parameters(&mut self, _type_parameters: &TypeParams) {
todo!("Infer type parameters")
fn infer_boolean_expression(&mut self, bool_op: &ast::ExprBoolOp) -> Type<'db> {
let ast::ExprBoolOp {
range: _,
op: _,
values,
} = bool_op;
for value in values {
self.infer_expression(value);
}
// TODO resolve bool op
Type::Unknown
}
fn infer_compare_expression(&mut self, compare: &ast::ExprCompare) -> Type<'db> {
let ast::ExprCompare {
range: _,
left,
ops: _,
comparators,
} = compare;
self.infer_expression(left);
// TODO actually handle ops and return correct type
for right in comparators.as_ref() {
self.infer_expression(right);
}
Type::Unknown
}
fn infer_subscript_expression(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> {
let ast::ExprSubscript {
range: _,
value,
slice,
ctx: _,
} = subscript;
self.infer_expression(slice);
self.infer_expression(value);
// TODO actual subscript support
Type::Unknown
}
fn infer_slice_expression(&mut self, slice: &ast::ExprSlice) -> Type<'db> {
let ast::ExprSlice {
range: _,
lower,
upper,
step,
} = slice;
self.infer_optional_expression(lower.as_deref());
self.infer_optional_expression(upper.as_deref());
self.infer_optional_expression(step.as_deref());
// TODO builtins.slice
Type::Unknown
}
fn infer_type_parameters(&mut self, type_parameters: &TypeParams) {
let ast::TypeParams {
range: _,
type_params,
} = type_parameters;
for type_param in type_params {
match type_param {
ast::TypeParam::TypeVar(typevar) => {
let ast::TypeParamTypeVar {
range: _,
name: _,
bound,
default,
} = typevar;
self.infer_optional_expression(bound.as_deref());
self.infer_optional_expression(default.as_deref());
}
ast::TypeParam::ParamSpec(param_spec) => {
let ast::TypeParamParamSpec {
range: _,
name: _,
default,
} = param_spec;
self.infer_optional_expression(default.as_deref());
}
ast::TypeParam::TypeVarTuple(typevar_tuple) => {
let ast::TypeParamTypeVarTuple {
range: _,
name: _,
default,
} = typevar_tuple;
self.infer_optional_expression(default.as_deref());
}
}
}
}
pub(super) fn finish(mut self) -> TypeInference<'db> {