Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/ty_module_resolver/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ impl KnownModule {
pub const fn is_functools(self) -> bool {
matches!(self, Self::Functools)
}

pub const fn is_dataclasses(self) -> bool {
matches!(self, Self::Dataclasses)
}
}

impl std::fmt::Display for KnownModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ from dataclasses import InitVar, dataclass

@dataclass
class Wrong:
x: InitVar[int, str] # error: [invalid-type-form] "Type qualifier `InitVar` expected exactly 1 argument, got 2"
x: InitVar[int, str] # error: [invalid-type-form] "Type qualifier `dataclasses.InitVar` expected exactly 1 argument, got 2"
```

A trailing comma in a subscript creates a single-element tuple. We need to handle this gracefully
Expand Down Expand Up @@ -165,5 +165,29 @@ class D:
self.x: InitVar[int] = 1 # error: [invalid-type-form] "`InitVar` annotations are not allowed for non-name targets"
```

### Use as a class

`InitVar` is a class at runtime. We do not recognise it as such, but we try to avoid emitting errors
on runtime uses of the symbol.

```py
from dataclasses import InitVar

x: type = InitVar

reveal_type(InitVar[int]) # revealed: Any
reveal_type(InitVar(int)) # revealed: Any
reveal_type(InitVar(type=int)) # revealed: Any

# error: [missing-argument] "No argument provided for required parameter `type`"
reveal_type(InitVar()) # revealed: Any
# error: [unknown-argument] "Argument `wut` does not match any known parameter"
reveal_type(InitVar(str, wut=56)) # revealed: Any

def test(x: object):
if isinstance(x, InitVar):
reveal_type(x) # revealed: Any
```

[type annotation grammar]: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions
[`dataclasses.initvar`]: https://docs.python.org/3/library/dataclasses.html#dataclasses.InitVar
18 changes: 7 additions & 11 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3736,6 +3736,13 @@ impl<'db> Type<'db> {
}
},

Type::SpecialForm(SpecialFormType::TypeQualifier(TypeQualifier::InitVar)) => {
let parameter = Parameter::positional_or_keyword(Name::new_static("type"))
.with_annotated_type(Type::any());
let signature = Signature::new(Parameters::new(db, [parameter]), Type::any());
Binding::single(self, signature).into()
}

Type::NominalInstance(_) | Type::ProtocolInstance(_) | Type::NewTypeInstance(_) => {
// Note that for objects that have a (possibly not callable!) `__call__` attribute,
// we will get the signature of the `__call__` attribute, but will pass in the type
Expand Down Expand Up @@ -4957,12 +4964,6 @@ impl<'db> Type<'db> {
let ty = match class.known(db) {
Some(KnownClass::Complex) => KnownUnion::Complex.to_type(db),
Some(KnownClass::Float) => KnownUnion::Float.to_type(db),
Some(KnownClass::InitVar) => {
return Err(InvalidTypeExpressionError {
invalid_expressions: smallvec_inline![InvalidTypeExpression::InitVar],
fallback_type: Type::unknown(),
});
}
_ => Type::instance(db, class.default_specialization(db)),
};
Ok(ty)
Expand Down Expand Up @@ -6678,7 +6679,6 @@ enum InvalidTypeExpression<'db> {
/// Type qualifiers that are invalid in type expressions,
/// and which would require exactly one argument even if they appeared in an annotation expression
TypeQualifierRequiresOneArgument(TypeQualifier),
InitVar,
/// `typing.Self` cannot be used in `@staticmethod` definitions.
TypingSelfInStaticMethod,
/// `typing.Self` cannot be used in metaclass definitions.
Expand Down Expand Up @@ -6752,10 +6752,6 @@ impl<'db> InvalidTypeExpression<'db> {
"Type qualifier `{qualifier}` is not allowed in type expressions \
(only in annotation expressions, and only with exactly one argument)",
),
InvalidTypeExpression::InitVar => f.write_str(
"Type qualifier `dataclasses.InitVar` is not allowed in type expressions \
(only in annotation expressions, and only with exactly one argument)",
),
InvalidTypeExpression::TypingSelfInStaticMethod => {
f.write_str("`Self` cannot be used in a static method")
}
Expand Down
16 changes: 2 additions & 14 deletions crates/ty_python_semantic/src/types/class/known.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ pub enum KnownClass {
// dataclasses
Field,
KwOnly,
InitVar,
// _typeshed._type_checker_internals
NamedTupleFallback,
NamedTupleLike,
Expand Down Expand Up @@ -243,7 +242,6 @@ impl KnownClass {
| Self::Deprecated
| Self::Field
| Self::KwOnly
| Self::InitVar
| Self::NamedTupleFallback
| Self::NamedTupleLike
| Self::ConstraintSet
Expand Down Expand Up @@ -334,7 +332,6 @@ impl KnownClass {
| KnownClass::NotImplementedType
| KnownClass::Field
| KnownClass::KwOnly
| KnownClass::InitVar
| KnownClass::NamedTupleFallback
| KnownClass::NamedTupleLike
| KnownClass::ConstraintSet
Expand Down Expand Up @@ -425,7 +422,6 @@ impl KnownClass {
| KnownClass::NotImplementedType
| KnownClass::Field
| KnownClass::KwOnly
| KnownClass::InitVar
| KnownClass::NamedTupleFallback
| KnownClass::NamedTupleLike
| KnownClass::ConstraintSet
Expand Down Expand Up @@ -515,7 +511,6 @@ impl KnownClass {
| KnownClass::NotImplementedType
| KnownClass::Field
| KnownClass::KwOnly
| KnownClass::InitVar
| KnownClass::TypedDictFallback
| KnownClass::NamedTupleLike
| KnownClass::NamedTupleFallback
Expand Down Expand Up @@ -617,7 +612,6 @@ impl KnownClass {
| Self::UnionType
| Self::Field
| Self::KwOnly
| Self::InitVar
| Self::NamedTupleFallback
| Self::ConstraintSet
| Self::GenericContext
Expand Down Expand Up @@ -720,8 +714,7 @@ impl KnownClass {
| KnownClass::Path
| KnownClass::ConstraintSet
| KnownClass::GenericContext
| KnownClass::Specialization
| KnownClass::InitVar => false,
| KnownClass::Specialization => false,
KnownClass::NamedTupleFallback | KnownClass::TypedDictFallback => true,
}
}
Expand Down Expand Up @@ -831,7 +824,6 @@ impl KnownClass {
}
Self::Field => "Field",
Self::KwOnly => "KW_ONLY",
Self::InitVar => "InitVar",
Self::NamedTupleFallback => "NamedTupleFallback",
Self::NamedTupleLike => "NamedTupleLike",
Self::ConstraintSet => "ConstraintSet",
Expand Down Expand Up @@ -1212,7 +1204,7 @@ impl KnownClass {
| Self::DefaultDict
| Self::Deque
| Self::OrderedDict => KnownModule::Collections,
Self::Field | Self::KwOnly | Self::InitVar => KnownModule::Dataclasses,
Self::Field | Self::KwOnly => KnownModule::Dataclasses,
Self::NamedTupleFallback | Self::TypedDictFallback => KnownModule::TypeCheckerInternals,
Self::NamedTupleLike
| Self::ConstraintSet
Expand Down Expand Up @@ -1297,7 +1289,6 @@ impl KnownClass {
| Self::NewType
| Self::Field
| Self::KwOnly
| Self::InitVar
| Self::Iterable
| Self::Iterator
| Self::AsyncIterator
Expand Down Expand Up @@ -1393,7 +1384,6 @@ impl KnownClass {
| Self::NewType
| Self::Field
| Self::KwOnly
| Self::InitVar
| Self::Iterable
| Self::Iterator
| Self::AsyncIterator
Expand Down Expand Up @@ -1508,7 +1498,6 @@ impl KnownClass {
}
"Field" => &[Self::Field],
"KW_ONLY" => &[Self::KwOnly],
"InitVar" => &[Self::InitVar],
"NamedTupleFallback" => &[Self::NamedTupleFallback],
"NamedTupleLike" => &[Self::NamedTupleLike],
"ConstraintSet" => &[Self::ConstraintSet],
Expand Down Expand Up @@ -1585,7 +1574,6 @@ impl KnownClass {
| Self::BuiltinFunctionType
| Self::Field
| Self::KwOnly
| Self::InitVar
| Self::NamedTupleFallback
| Self::TypedDictFallback
| Self::TypeVar
Expand Down
19 changes: 5 additions & 14 deletions crates/ty_python_semantic/src/types/infer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity};
use ruff_db::files::File;
use ruff_db::parsed::ParsedModuleRef;
use ruff_db::source::source_text;
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::name::Name;
use ruff_python_ast::{
self as ast, AnyNodeRef, ArgOrKeyword, ArgumentsSourceOrder, ExprContext, HasNodeIndex,
Expand Down Expand Up @@ -4258,20 +4259,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
};

if is_pep_613_type_alias {
let is_valid_special_form = |ty: Type<'db>| match ty {
Type::SpecialForm(SpecialFormType::TypeQualifier(_)) => false,
Type::ClassLiteral(literal) => {
!literal.is_known(self.db(), KnownClass::InitVar)
}
_ => true,
};

let is_invalid = match value {
ast::Expr::Subscript(sub) => {
!is_valid_special_form(self.expression_type(&sub.value))
}
_ => !is_valid_special_form(self.expression_type(value)),
};
let is_invalid = matches!(
self.expression_type(map_subscript(value)),
Type::SpecialForm(SpecialFormType::TypeQualifier(_))
);

if is_invalid
&& let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ use crate::types::string_annotation::{
BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation,
};
use crate::types::{
KnownClass, SpecialFormType, Type, TypeAndQualifiers, TypeContext, TypeQualifier,
TypeQualifiers, todo_type,
SpecialFormType, Type, TypeAndQualifiers, TypeContext, TypeQualifier, TypeQualifiers, todo_type,
};

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -100,6 +99,20 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
) -> TypeAndQualifiers<'db> {
let special_case = match ty {
Type::SpecialForm(special_form) => match special_form {
SpecialFormType::TypeQualifier(TypeQualifier::InitVar) => {
if let Some(builder) =
builder.context.report_lint(&INVALID_TYPE_FORM, annotation)
{
builder.into_diagnostic(
"`InitVar` may not be used without a type argument",
);
}
Some(TypeAndQualifiers::new(
Type::unknown(),
TypeOrigin::Declared,
TypeQualifiers::INIT_VAR,
))
}
SpecialFormType::TypeQualifier(qualifier) => Some(TypeAndQualifiers::new(
Type::unknown(),
TypeOrigin::Declared,
Expand All @@ -125,19 +138,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
SpecialFormType::TypeAlias,
)))
}
Type::ClassLiteral(class) if class.is_known(builder.db(), KnownClass::InitVar) => {
if let Some(builder) =
builder.context.report_lint(&INVALID_TYPE_FORM, annotation)
{
builder
.into_diagnostic("`InitVar` may not be used without a type argument");
}
Some(TypeAndQualifiers::new(
Type::unknown(),
TypeOrigin::Declared,
TypeQualifiers::INIT_VAR,
))
}
_ => None,
};

Expand Down Expand Up @@ -360,41 +360,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
),
),
},
Type::ClassLiteral(class) if class.is_known(self.db(), KnownClass::InitVar) => {
let arguments = if let ast::Expr::Tuple(tuple) = slice {
&*tuple.elts
} else {
std::slice::from_ref(slice)
};
let type_and_qualifiers = if let [argument] = arguments {
self.infer_annotation_expression_impl(
argument,
PEP613Policy::Disallowed,
)
.with_qualifier(TypeQualifiers::INIT_VAR)
} else {
for element in arguments {
self.infer_annotation_expression_impl(
element,
PEP613Policy::Disallowed,
);
}
if let Some(builder) =
self.context.report_lint(&INVALID_TYPE_FORM, subscript)
{
let num_arguments = arguments.len();
builder.into_diagnostic(format_args!(
"Type qualifier `InitVar` expected exactly 1 argument, \
got {num_arguments}",
));
}
TypeAndQualifiers::declared(Type::unknown())
};
if slice.is_tuple_expr() {
self.store_expression_type(slice, type_and_qualifiers.inner_type());
}
type_and_qualifiers
}
_ => TypeAndQualifiers::declared(
self.infer_subscript_type_expression_no_store(subscript, slice, value_ty),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
builder::{DeclaredAndInferredType, DeferredExpressionState},
},
signatures::ParameterForm,
special_form::TypeQualifier,
},
};
use ruff_python_ast::{self as ast, helpers::any_over_expr};
Expand Down Expand Up @@ -166,9 +167,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {

let maybe_known_class = KnownClass::try_from_file_and_name(db, self.file(), name);

let known_module = || file_to_module(db, self.file()).and_then(|module| module.known(db));
let in_typing_module = || {
matches!(
file_to_module(db, self.file()).and_then(|module| module.known(db)),
known_module(),
Some(KnownModule::Typing | KnownModule::TypingExtensions)
)
};
Expand All @@ -178,6 +180,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
Type::SpecialForm(SpecialFormType::NamedTuple)
}
(None, "Any") if in_typing_module() => Type::SpecialForm(SpecialFormType::Any),
(None, "InitVar") if known_module() == Some(KnownModule::Dataclasses) => {
Type::SpecialForm(SpecialFormType::TypeQualifier(TypeQualifier::InitVar))
}
_ => Type::from(StaticClassLiteral::new(
db,
name.id.clone(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ use super::{DeferredExpressionState, TypeInferenceBuilder};
use crate::semantic_index::scope::ScopeKind;
use crate::types::diagnostic::{
self, INVALID_TYPE_FORM, NOT_SUBSCRIPTABLE, UNBOUND_TYPE_VARIABLE, UNSUPPORTED_OPERATOR,
add_type_expression_reference_link, note_py_version_too_old_for_pep_604,
report_invalid_argument_number_to_special_form, report_invalid_arguments_to_callable,
report_invalid_concatenate_last_arg,
note_py_version_too_old_for_pep_604, report_invalid_argument_number_to_special_form,
report_invalid_arguments_to_callable, report_invalid_concatenate_last_arg,
};
use crate::types::infer::InferenceFlags;
use crate::types::signatures::{ConcatenateTail, Signature};
Expand Down Expand Up @@ -684,17 +683,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
Type::ClassLiteral(class_literal) => match class_literal.known(self.db()) {
Some(KnownClass::Tuple) => Type::tuple(self.infer_tuple_type_expression(subscript)),
Some(KnownClass::Type) => self.infer_subclass_of_type_expression(slice),
Some(KnownClass::InitVar) => {
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
let diagnostic = builder.into_diagnostic(
"Type qualifier `dataclasses.InitVar` is not allowed in type \
expressions (only in annotation expressions)",
);
add_type_expression_reference_link(diagnostic);
}
self.infer_expression(slice, TypeContext::default());
Type::unknown()
}
_ => self.infer_subscript_type_expression(subscript, value_ty),
},
_ => self.infer_subscript_type_expression(subscript, value_ty),
Expand Down
Loading
Loading