[ty] Add a note to the diagnostic if a new builtin is used on an old Python version (#18068)

## Summary

If the user tries to use a new builtin on an old Python version, tell
them what Python version the builtin was added on, what our inferred
Python version is for their project, and what configuration settings
they can tweak to fix the error.

## Test Plan

Snapshots and screenshots:


![image](https://github.com/user-attachments/assets/767d570e-7af1-4e1f-98cf-50e4311db511)
This commit is contained in:
Alex Waygood 2025-05-13 10:08:04 -04:00 committed by GitHub
parent 5bf5f3682a
commit c0f22928bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 118 additions and 54 deletions

View file

@ -2,7 +2,6 @@ use super::context::InferContext;
use super::mro::DuplicateBaseError;
use super::{ClassLiteral, KnownClass};
use crate::db::Db;
use crate::declare_lint;
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
use crate::suppression::FileSuppressionId;
use crate::types::string_annotation::{
@ -11,8 +10,10 @@ use crate::types::string_annotation::{
RAW_STRING_TYPE_ANNOTATION,
};
use crate::types::{protocol_class::ProtocolClassLiteral, KnownFunction, KnownInstanceType, Type};
use crate::{declare_lint, Program};
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_python_stdlib::builtins::version_builtin_was_added;
use ruff_text_size::{Ranged, TextRange};
use rustc_hash::FxHashSet;
use std::fmt::Formatter;
@ -1650,7 +1651,24 @@ pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node
};
let ast::ExprName { id, .. } = expr_name_node;
builder.into_diagnostic(format_args!("Name `{id}` used when not defined"));
let mut diagnostic = builder.into_diagnostic(format_args!("Name `{id}` used when not defined"));
if let Some(version_added_to_builtins) = version_builtin_was_added(id) {
diagnostic.info(format_args!(
"`{id}` was added as a builtin in Python 3.{version_added_to_builtins}"
));
// 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"
);
}
}
pub(super) fn report_invalid_exception_caught(context: &InferContext, node: &ast::Expr, ty: Type) {