mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-19 03:48:29 +00:00
[ty] Use 3.14 as the default version (#20759)
## Summary Bump the latest supported Python version of ty to 3.14 and updates some references from 3.13 to 3.14. This also fixes a bug with `dataclasses.field` on 3.14 (which adds a new keyword-only parameter to that function, breaking our previously naive matching on the parameter structure of that function). ## Test Plan A `ty check` on a file with template strings (without any further configuration) doesn't raise errors anymore.
This commit is contained in:
parent
abbbe8f3af
commit
1f1542db51
10 changed files with 117 additions and 50 deletions
|
|
@ -67,8 +67,8 @@ impl PythonVersion {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn latest_ty() -> Self {
|
pub const fn latest_ty() -> Self {
|
||||||
// Make sure to update the default value for `EnvironmentOptions::python_version` when bumping this version.
|
// Make sure to update the default value for `EnvironmentOptions::python_version` when bumping this version.
|
||||||
Self::PY313
|
Self::PY314
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn as_tuple(self) -> (u8, u8) {
|
pub const fn as_tuple(self) -> (u8, u8) {
|
||||||
|
|
|
||||||
2
crates/ty/docs/cli.md
generated
2
crates/ty/docs/cli.md
generated
|
|
@ -76,7 +76,7 @@ over all configuration files.</p>
|
||||||
<p>This is used to specialize the type of <code>sys.platform</code> and will affect the visibility of platform-specific functions and attributes. If the value is set to <code>all</code>, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.</p>
|
<p>This is used to specialize the type of <code>sys.platform</code> and will affect the visibility of platform-specific functions and attributes. If the value is set to <code>all</code>, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.</p>
|
||||||
</dd><dt id="ty-check--python-version"><a href="#ty-check--python-version"><code>--python-version</code></a>, <code>--target-version</code> <i>version</i></dt><dd><p>Python version to assume when resolving types.</p>
|
</dd><dt id="ty-check--python-version"><a href="#ty-check--python-version"><code>--python-version</code></a>, <code>--target-version</code> <i>version</i></dt><dd><p>Python version to assume when resolving types.</p>
|
||||||
<p>The Python version affects allowed syntax, type definitions of the standard library, and type definitions of first- and third-party modules that are conditional on the Python version.</p>
|
<p>The Python version affects allowed syntax, type definitions of the standard library, and type definitions of first- and third-party modules that are conditional on the Python version.</p>
|
||||||
<p>If a version is not specified on the command line or in a configuration file, ty will try the following techniques in order of preference to determine a value: 1. Check for the <code>project.requires-python</code> setting in a <code>pyproject.toml</code> file and use the minimum version from the specified range 2. Check for an activated or configured Python environment and attempt to infer the Python version of that environment 3. Fall back to the latest stable Python version supported by ty (currently Python 3.13)</p>
|
<p>If a version is not specified on the command line or in a configuration file, ty will try the following techniques in order of preference to determine a value: 1. Check for the <code>project.requires-python</code> setting in a <code>pyproject.toml</code> file and use the minimum version from the specified range 2. Check for an activated or configured Python environment and attempt to infer the Python version of that environment 3. Fall back to the latest stable Python version supported by ty (see <code>ty check --help</code> output)</p>
|
||||||
<p>Possible values:</p>
|
<p>Possible values:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>3.7</code></li>
|
<li><code>3.7</code></li>
|
||||||
|
|
|
||||||
4
crates/ty/docs/configuration.md
generated
4
crates/ty/docs/configuration.md
generated
|
|
@ -133,9 +133,9 @@ For some language features, ty can also understand conditionals based on compari
|
||||||
with `sys.version_info`. These are commonly found in typeshed, for example,
|
with `sys.version_info`. These are commonly found in typeshed, for example,
|
||||||
to reflect the differing contents of the standard library across Python versions.
|
to reflect the differing contents of the standard library across Python versions.
|
||||||
|
|
||||||
**Default value**: `"3.13"`
|
**Default value**: `"3.14"`
|
||||||
|
|
||||||
**Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | <major>.<minor>`
|
**Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | "3.14" | <major>.<minor>`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage** (`pyproject.toml`):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ pub(crate) struct CheckCommand {
|
||||||
/// and use the minimum version from the specified range
|
/// and use the minimum version from the specified range
|
||||||
/// 2. Check for an activated or configured Python environment
|
/// 2. Check for an activated or configured Python environment
|
||||||
/// and attempt to infer the Python version of that environment
|
/// and attempt to infer the Python version of that environment
|
||||||
/// 3. Fall back to the latest stable Python version supported by ty (currently Python 3.13)
|
/// 3. Fall back to the latest stable Python version supported by ty (see `ty check --help` output)
|
||||||
#[arg(long, value_name = "VERSION", alias = "target-version")]
|
#[arg(long, value_name = "VERSION", alias = "target-version")]
|
||||||
pub(crate) python_version: Option<PythonVersion>,
|
pub(crate) python_version: Option<PythonVersion>,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1732,6 +1732,7 @@ C.<CURSOR>
|
||||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||||
meta_attr :: int
|
meta_attr :: int
|
||||||
mro :: bound method <class 'C'>.mro() -> list[type]
|
mro :: bound method <class 'C'>.mro() -> list[type]
|
||||||
|
__annotate__ :: @Todo | None
|
||||||
__annotations__ :: dict[str, Any]
|
__annotations__ :: dict[str, Any]
|
||||||
__base__ :: type | None
|
__base__ :: type | None
|
||||||
__bases__ :: tuple[type, ...]
|
__bases__ :: tuple[type, ...]
|
||||||
|
|
@ -1797,7 +1798,7 @@ Meta.<CURSOR>
|
||||||
// whether we're in release mode or not. These differences
|
// whether we're in release mode or not. These differences
|
||||||
// aren't really relevant for completion tests AFAIK, so
|
// aren't really relevant for completion tests AFAIK, so
|
||||||
// just redact them. ---AG
|
// just redact them. ---AG
|
||||||
filters => [(r"(?m)\s*__(annotations|new)__.+$", "")]},
|
filters => [(r"(?m)\s*__(annotations|new|annotate)__.+$", "")]},
|
||||||
{
|
{
|
||||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||||
meta_attr :: property
|
meta_attr :: property
|
||||||
|
|
@ -1908,6 +1909,7 @@ Quux.<CURSOR>
|
||||||
some_method :: def some_method(self) -> int
|
some_method :: def some_method(self) -> int
|
||||||
some_property :: property
|
some_property :: property
|
||||||
some_static_method :: def some_static_method(self) -> int
|
some_static_method :: def some_static_method(self) -> int
|
||||||
|
__annotate__ :: @Todo | None
|
||||||
__annotations__ :: dict[str, Any]
|
__annotations__ :: dict[str, Any]
|
||||||
__base__ :: type | None
|
__base__ :: type | None
|
||||||
__bases__ :: tuple[type, ...]
|
__bases__ :: tuple[type, ...]
|
||||||
|
|
@ -1970,7 +1972,7 @@ Answer.<CURSOR>
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
// See above: filter out some members which contain @Todo types that are
|
// See above: filter out some members which contain @Todo types that are
|
||||||
// rendered differently in release mode.
|
// rendered differently in release mode.
|
||||||
filters => [(r"(?m)\s*__(call|reduce_ex)__.+$", "")]},
|
filters => [(r"(?m)\s*__(call|reduce_ex|annotate|signature)__.+$", "")]},
|
||||||
{
|
{
|
||||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||||
NO :: Literal[Answer.NO]
|
NO :: Literal[Answer.NO]
|
||||||
|
|
@ -2020,7 +2022,6 @@ Answer.<CURSOR>
|
||||||
__reversed__ :: bound method <class 'Answer'>.__reversed__[_EnumMemberT]() -> Iterator[_EnumMemberT@__reversed__]
|
__reversed__ :: bound method <class 'Answer'>.__reversed__[_EnumMemberT]() -> Iterator[_EnumMemberT@__reversed__]
|
||||||
__ror__ :: bound method <class 'Answer'>.__ror__(value: Any, /) -> UnionType
|
__ror__ :: bound method <class 'Answer'>.__ror__(value: Any, /) -> UnionType
|
||||||
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
|
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
|
||||||
__signature__ :: bound method <class 'Answer'>.__signature__() -> str
|
|
||||||
__sizeof__ :: def __sizeof__(self) -> int
|
__sizeof__ :: def __sizeof__(self) -> int
|
||||||
__str__ :: def __str__(self) -> str
|
__str__ :: def __str__(self) -> str
|
||||||
__subclasscheck__ :: bound method <class 'Answer'>.__subclasscheck__(subclass: type, /) -> bool
|
__subclasscheck__ :: bound method <class 'Answer'>.__subclasscheck__(subclass: type, /) -> bool
|
||||||
|
|
|
||||||
|
|
@ -520,8 +520,8 @@ pub struct EnvironmentOptions {
|
||||||
/// to reflect the differing contents of the standard library across Python versions.
|
/// to reflect the differing contents of the standard library across Python versions.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[option(
|
#[option(
|
||||||
default = r#""3.13""#,
|
default = r#""3.14""#,
|
||||||
value_type = r#""3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | <major>.<minor>"#,
|
value_type = r#""3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | "3.14" | <major>.<minor>"#,
|
||||||
example = r#"
|
example = r#"
|
||||||
python-version = "3.12"
|
python-version = "3.12"
|
||||||
"#
|
"#
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,55 @@ class A:
|
||||||
y: int
|
y: int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `kw_only` - Python 3.13
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.13"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Employee:
|
||||||
|
e_id: int = field(kw_only=True, default=0)
|
||||||
|
name: str
|
||||||
|
|
||||||
|
Employee("Alice")
|
||||||
|
Employee(name="Alice")
|
||||||
|
Employee(name="Alice", e_id=1)
|
||||||
|
Employee(e_id=1, name="Alice")
|
||||||
|
Employee("Alice", e_id=1)
|
||||||
|
|
||||||
|
Employee("Alice", 1) # error: [too-many-positional-arguments]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `kw_only` - Python 3.14
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.14"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Employee:
|
||||||
|
# Python 3.14 introduces a new `doc` parameter for `dataclasses.field`
|
||||||
|
e_id: int = field(kw_only=True, default=0, doc="Global employee ID")
|
||||||
|
name: str
|
||||||
|
|
||||||
|
Employee("Alice")
|
||||||
|
Employee(name="Alice")
|
||||||
|
Employee(name="Alice", e_id=1)
|
||||||
|
Employee(e_id=1, name="Alice")
|
||||||
|
Employee("Alice", e_id=1)
|
||||||
|
|
||||||
|
Employee("Alice", 1) # error: [too-many-positional-arguments]
|
||||||
|
```
|
||||||
|
|
||||||
### `slots`
|
### `slots`
|
||||||
|
|
||||||
If a dataclass is defined with `slots=True`, the `__slots__` attribute is generated as a tuple. It
|
If a dataclass is defined with `slots=True`, the `__slots__` attribute is generated as a tuple. It
|
||||||
|
|
|
||||||
|
|
@ -962,43 +962,46 @@ impl<'db> Bindings<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(KnownFunction::Field) => {
|
Some(KnownFunction::Field) => {
|
||||||
// TODO this will break on Python 3.14 -- we should match by parameter name instead
|
let default =
|
||||||
if let [default, default_factory, init, .., kw_only] =
|
overload.parameter_type_by_name("default").unwrap_or(None);
|
||||||
overload.parameter_types()
|
let default_factory = overload
|
||||||
{
|
.parameter_type_by_name("default_factory")
|
||||||
let default_ty = match (default, default_factory) {
|
.unwrap_or(None);
|
||||||
(Some(default_ty), _) => *default_ty,
|
let init = overload.parameter_type_by_name("init").unwrap_or(None);
|
||||||
(_, Some(default_factory_ty)) => default_factory_ty
|
let kw_only =
|
||||||
.try_call(db, &CallArguments::none())
|
overload.parameter_type_by_name("kw_only").unwrap_or(None);
|
||||||
.map_or(Type::unknown(), |binding| binding.return_type(db)),
|
|
||||||
_ => Type::unknown(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let init = init
|
let default_ty = match (default, default_factory) {
|
||||||
.map(|init| !init.bool(db).is_always_false())
|
(Some(default_ty), _) => default_ty,
|
||||||
.unwrap_or(true);
|
(_, Some(default_factory_ty)) => default_factory_ty
|
||||||
|
.try_call(db, &CallArguments::none())
|
||||||
|
.map_or(Type::unknown(), |binding| binding.return_type(db)),
|
||||||
|
_ => Type::unknown(),
|
||||||
|
};
|
||||||
|
|
||||||
let kw_only = if Program::get(db).python_version(db)
|
let init = init
|
||||||
>= PythonVersion::PY310
|
.map(|init| !init.bool(db).is_always_false())
|
||||||
{
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
let kw_only =
|
||||||
|
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||||
kw_only.map(|kw_only| !kw_only.bool(db).is_always_false())
|
kw_only.map(|kw_only| !kw_only.bool(db).is_always_false())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// `typeshed` pretends that `dataclasses.field()` returns the type of the
|
// `typeshed` pretends that `dataclasses.field()` returns the type of the
|
||||||
// default value directly. At runtime, however, this function returns an
|
// default value directly. At runtime, however, this function returns an
|
||||||
// instance of `dataclasses.Field`. We also model it this way and return
|
// instance of `dataclasses.Field`. We also model it this way and return
|
||||||
// a known-instance type with information about the field. The drawback
|
// a known-instance type with information about the field. The drawback
|
||||||
// of this approach is that we need to pretend that instances of `Field`
|
// of this approach is that we need to pretend that instances of `Field`
|
||||||
// are assignable to `T` if the default type of the field is assignable
|
// are assignable to `T` if the default type of the field is assignable
|
||||||
// to `T`. Otherwise, we would error on `name: str = field(default="")`.
|
// to `T`. Otherwise, we would error on `name: str = field(default="")`.
|
||||||
overload.set_return_type(Type::KnownInstance(
|
overload.set_return_type(Type::KnownInstance(
|
||||||
KnownInstanceType::Field(FieldInstance::new(
|
KnownInstanceType::Field(FieldInstance::new(
|
||||||
db, default_ty, init, kw_only,
|
db, default_ty, init, kw_only,
|
||||||
)),
|
)),
|
||||||
));
|
));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -2782,6 +2785,9 @@ impl<'db> MatchedArgument<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Indicates that a parameter of the given name was not found.
|
||||||
|
pub(crate) struct UnknownParameterNameError;
|
||||||
|
|
||||||
/// Binding information for one of the overloads of a callable.
|
/// Binding information for one of the overloads of a callable.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Binding<'db> {
|
pub(crate) struct Binding<'db> {
|
||||||
|
|
@ -2919,6 +2925,25 @@ impl<'db> Binding<'db> {
|
||||||
&self.parameter_tys
|
&self.parameter_tys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the bound type for the specified parameter, or `None` if no argument was matched to
|
||||||
|
/// that parameter.
|
||||||
|
///
|
||||||
|
/// Returns an error if the parameter name is not found.
|
||||||
|
pub(crate) fn parameter_type_by_name(
|
||||||
|
&self,
|
||||||
|
parameter_name: &str,
|
||||||
|
) -> Result<Option<Type<'db>>, UnknownParameterNameError> {
|
||||||
|
let (index, _) = self
|
||||||
|
.signature
|
||||||
|
.parameters()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, param)| param.name().is_some_and(|name| name == parameter_name))
|
||||||
|
.ok_or(UnknownParameterNameError)?;
|
||||||
|
|
||||||
|
Ok(self.parameter_tys[index])
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn arguments_for_parameter<'a>(
|
pub(crate) fn arguments_for_parameter<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
argument_types: &'a CallArguments<'a, 'db>,
|
argument_types: &'a CallArguments<'a, 'db>,
|
||||||
|
|
|
||||||
|
|
@ -5510,14 +5510,6 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
for class in KnownClass::iter() {
|
for class in KnownClass::iter() {
|
||||||
// Until the latest supported version is bumped to Python 3.14
|
|
||||||
// we need to skip template strings here.
|
|
||||||
// The assertion below should remind the developer to
|
|
||||||
// remove this exception once we _do_ bump `latest_ty`
|
|
||||||
assert_ne!(PythonVersion::latest_ty(), PythonVersion::PY314);
|
|
||||||
if matches!(class, KnownClass::Template) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
class.to_instance(&db),
|
class.to_instance(&db),
|
||||||
Type::unknown(),
|
Type::unknown(),
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ pub(crate) struct Environment {
|
||||||
///
|
///
|
||||||
/// By default, the Python version is inferred as the lower bound of the project's
|
/// By default, the Python version is inferred as the lower bound of the project's
|
||||||
/// `requires-python` field from the `pyproject.toml`, if available. Otherwise, the latest
|
/// `requires-python` field from the `pyproject.toml`, if available. Otherwise, the latest
|
||||||
/// stable version supported by ty is used, which is currently 3.13.
|
/// stable version supported by ty is used (see `ty check --help` output).
|
||||||
///
|
///
|
||||||
/// ty will not infer the Python version from the Python environment at this time.
|
/// ty will not infer the Python version from the Python environment at this time.
|
||||||
pub(crate) python_version: Option<PythonVersion>,
|
pub(crate) python_version: Option<PythonVersion>,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue