mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:47 +00:00
[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:
parent
7571da8778
commit
2d3914296d
276 changed files with 2002 additions and 92 deletions
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue