[ty] Add hint that PEP 604 union syntax is only available in 3.10+ (#18192)

## Summary

Add a new diagnostic hint if you try to use PEP 604 `X | Y` union syntax
in a non-type-expression before 3.10.

closes https://github.com/astral-sh/ty/issues/437

## Test Plan

New snapshot test
This commit is contained in:
David Peter 2025-05-19 19:47:31 +02:00 committed by GitHub
parent d6009eb942
commit 0ede831a3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 92 additions and 14 deletions

View file

@ -1669,6 +1669,18 @@ pub(super) fn report_possibly_unbound_attribute(
));
}
pub(super) fn add_inferred_python_version_hint(db: &dyn Db, mut diagnostic: LintDiagnosticGuard) {
diagnostic.info(format_args!(
"The inferred target version of your project is Python {}",
Program::get(db).python_version(db)
));
diagnostic.info(
"If using a pyproject.toml file, \
consider adjusting the `project.requires-python` or `tool.ty.environment.python-version` field"
);
}
pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node: &ast::ExprName) {
let Some(builder) = context.report_lint(&UNRESOLVED_REFERENCE, expr_name_node) else {
return;
@ -1683,15 +1695,7 @@ pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node
// TODO: can we tell the user *why* we're inferring this target version?
// CLI flag? pyproject.toml? Python environment?
diagnostic.info(format_args!(
"The inferred target version of your project is Python {}",
Program::get(context.db()).python_version(context.db())
));
diagnostic.info(
"If using a pyproject.toml file, \
consider adjusting the `project.requires-python` or `tool.ty.environment.python-version` field"
);
add_inferred_python_version_hint(context.db(), diagnostic);
}
}

View file

@ -38,7 +38,7 @@ use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity};
use ruff_db::files::File;
use ruff_db::parsed::parsed_module;
use ruff_python_ast::visitor::{Visitor, walk_expr};
use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext};
use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext, PythonVersion};
use ruff_text_size::{Ranged, TextRange};
use rustc_hash::{FxHashMap, FxHashSet};
use salsa;
@ -94,7 +94,7 @@ use crate::types::{
};
use crate::unpack::{Unpack, UnpackPosition};
use crate::util::subscript::{PyIndex, PySlice};
use crate::{Db, FxOrderSet};
use crate::{Db, FxOrderSet, Program};
use super::context::{InNoTypeCheck, InferContext};
use super::diagnostic::{
@ -5934,12 +5934,25 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_binary_expression_type(binary.into(), false, left_ty, right_ty, *op)
.unwrap_or_else(|| {
let db = self.db();
if let Some(builder) = self.context.report_lint(&UNSUPPORTED_OPERATOR, binary) {
builder.into_diagnostic(format_args!(
let mut diag = builder.into_diagnostic(format_args!(
"Operator `{op}` is unsupported between objects of type `{}` and `{}`",
left_ty.display(self.db()),
right_ty.display(self.db())
left_ty.display(db),
right_ty.display(db)
));
if op == &ast::Operator::BitOr
&& (left_ty.is_subtype_of(db, KnownClass::Type.to_instance(db))
|| right_ty.is_subtype_of(db, KnownClass::Type.to_instance(db)))
&& Program::get(db).python_version(db) < PythonVersion::PY310
{
diag.info(
"Note that `X | Y` PEP 604 union syntax is only available in Python 3.10 and later",
);
diagnostic::add_inferred_python_version_hint(db, diag);
}
}
Type::unknown()
})