diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index 869c6e53b1..6d40981339 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -469,7 +469,8 @@ reveal_type(Point2.__new__) # revealed: (cls: type, _0: Any, _1: Any) -> Point2 # `defaults` provides default values for the rightmost fields Person = collections.namedtuple("Person", ["name", "age", "city"], defaults=["Unknown"]) reveal_type(Person) # revealed: -reveal_type(Person.__new__) # revealed: (cls: type, name: Any, age: Any, city: Any = ...) -> Person +reveal_type(Person.__new__) # revealed: (cls: type, name: Any, age: Any, city: Any = "Unknown") -> Person + reveal_mro(Person) # revealed: (, , ) # Can create with all fields person1 = Person("Alice", 30, "NYC") @@ -486,7 +487,7 @@ reveal_type(Config) # revealed: # TODO: This should emit a diagnostic since it would fail at runtime. TooManyDefaults = collections.namedtuple("TooManyDefaults", ["x", "y"], defaults=("a", "b", "c")) reveal_type(TooManyDefaults) # revealed: -reveal_type(TooManyDefaults.__new__) # revealed: (cls: type, x: Any = ..., y: Any = ...) -> TooManyDefaults +reveal_type(TooManyDefaults.__new__) # revealed: (cls: type, x: Any = "a", y: Any = "b") -> TooManyDefaults # Unknown keyword arguments produce an error # error: [unknown-argument] diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 10965bc424..591aa43322 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -6564,7 +6564,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } // Infer keyword arguments. - let mut defaults_count = None; + let mut default_types: Vec> = vec![]; let mut rename_type = None; for kw in keywords { @@ -6575,11 +6575,42 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; match arg.id.as_str() { "defaults" if kind.is_collections() => { - defaults_count = kw_type - .exact_tuple_instance_spec(db) - .and_then(|spec| spec.len().maximum()) - .or_else(|| kw.value.as_list_expr().map(|list| list.elts.len())); - + // Extract element types from AST literals (using already-inferred types) + // or fall back to the inferred tuple spec. + match &kw.value { + ast::Expr::List(list) => { + // Elements were already inferred when we inferred kw.value above. + default_types = list + .elts + .iter() + .map(|elt| self.expression_type(elt)) + .collect(); + } + ast::Expr::Tuple(tuple) => { + // Elements were already inferred when we inferred kw.value above. + default_types = tuple + .elts + .iter() + .map(|elt| self.expression_type(elt)) + .collect(); + } + _ => { + // Fall back to using the already-inferred type. + // Try to extract element types from tuple. + if let Some(spec) = kw_type.exact_tuple_instance_spec(db) + && let Some(fixed) = spec.as_fixed_length() + { + default_types = fixed.all_elements().to_vec(); + } else { + // Can't determine individual types; use Any for each element. + let count = kw_type + .exact_tuple_instance_spec(db) + .and_then(|spec| spec.len().maximum()) + .unwrap_or(0); + default_types = vec![Type::any(); count]; + } + } + } // Emit diagnostic for invalid types (not Iterable[Any] | None). let iterable_any = KnownClass::Iterable.to_specialized_instance(db, &[Type::any()]); @@ -6644,8 +6675,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } - let defaults_count = defaults_count.unwrap_or_default(); - // Extract name. let name = if let Type::StringLiteral(literal) = name_type { Name::new(literal.value(db)) @@ -6778,14 +6807,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // TODO: emit a diagnostic when `defaults_count > num_fields` (which would // fail at runtime with `TypeError: Got more default values than field names`). let num_fields = field_names.len(); - let defaults_count = defaults_count.min(num_fields); + let defaults_count = default_types.len().min(num_fields); let fields = field_names .iter() .enumerate() .map(|(i, field_name)| { let default = if defaults_count > 0 && i >= num_fields - defaults_count { - Some(Type::any()) + // Index into default_types: first default corresponds to first + // field that has a default. + let default_idx = i - (num_fields - defaults_count); + Some(default_types[default_idx]) } else { None };