diff --git a/crates/ty_python_semantic/resources/mdtest/directives/reveal_type.md b/crates/ty_python_semantic/resources/mdtest/directives/reveal_type.md new file mode 100644 index 0000000000000..44648443fe1de --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/directives/reveal_type.md @@ -0,0 +1,68 @@ +# `reveal_type` + +`reveal_type` is used to inspect the type of an expression at a given point in the code. It is often +used for debugging and understanding how types are inferred by the type checker. + +## Basic usage + +```py +from typing_extensions import reveal_type + +reveal_type(1) # revealed: Literal[1] +``` + +This also works with the fully qualified name: + +```py +import typing_extensions + +typing_extensions.reveal_type(1) # revealed: Literal[1] +``` + +The return type of `reveal_type` is the type of the argument: + +```py +from typing_extensions import assert_type + +def _(x: int): + y = reveal_type(x) # revealed: int + assert_type(y, int) +``` + +## Without importing it + +For convenience, we also allow `reveal_type` to be used without importing it, even if that would +fail at runtime: + +```py +reveal_type(1) # revealed: Literal[1] +``` + +## In unreachable code + +Make sure that `reveal_type` works even in unreachable code. + +### When importing it + +```py +from typing_extensions import reveal_type +import typing_extensions + +if False: + reveal_type(1) # revealed: Literal[1] + typing_extensions.reveal_type(1) # revealed: Literal[1] + +if 1 + 1 != 2: + reveal_type(1) # revealed: Literal[1] + typing_extensions.reveal_type(1) # revealed: Literal[1] +``` + +### Without importing it + +```py +if False: + reveal_type(1) # revealed: Literal[1] + +if 1 + 1 != 2: + reveal_type(1) # revealed: Literal[1] +``` diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 47358d0693fbc..49bc87c8d5c53 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -1835,17 +1835,7 @@ impl KnownFunction { .arguments_for_parameter(call_arguments, 0) .fold(UnionBuilder::new(db), |builder, (_, ty)| builder.add(ty)) .build(); - if let Some(builder) = - context.report_diagnostic(DiagnosticId::RevealedType, Severity::Info) - { - let mut diag = builder.into_diagnostic("Revealed type"); - let span = context.span(&call_expression.arguments.args[0]); - diag.annotate(Annotation::primary(span).message(format_args!( - "`{}`", - revealed_type - .display_with(db, DisplaySettings::default().preserve_long_unions()) - ))); - } + report_revealed_type(context, revealed_type, &call_expression.arguments.args[0]); } KnownFunction::HasMember => { @@ -2235,6 +2225,26 @@ impl KnownFunction { } } +/// Emit a `revealed-type` diagnostic for a `reveal_type(...)` call. +pub(super) fn report_revealed_type<'db>( + context: &InferContext<'db, '_>, + revealed_type: Type<'db>, + argument_node: &ast::Expr, +) { + if let Some(builder) = context.report_diagnostic(DiagnosticId::RevealedType, Severity::Info) { + let mut diag = builder.into_diagnostic("Revealed type"); + diag.annotate( + Annotation::primary(context.span(argument_node)).message(format_args!( + "`{}`", + revealed_type.display_with( + context.db(), + DisplaySettings::default().preserve_long_unions() + ) + )), + ); + } +} + #[cfg(test)] pub(crate) mod tests { use strum::IntoEnumIterator; diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 340f8ccbdcf8c..172e2097d79e6 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -84,7 +84,7 @@ use crate::types::diagnostic::{ report_unsupported_comparison, }; use crate::types::enums::{enum_ignored_names, is_enum_class_by_inheritance}; -use crate::types::function::{FunctionType, KnownFunction}; +use crate::types::function::{FunctionType, KnownFunction, report_revealed_type}; use crate::types::generics::{InferableTypeVars, SpecializationBuilder, bind_typevar}; use crate::types::infer::builder::named_tuple::NamedTupleKind; use crate::types::infer::builder::paramspec_validation::validate_paramspec_components; @@ -7199,6 +7199,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ); } } + Type::Never => { + // In unreachable sections of code, we infer `Never` for symbols that were + // defined outside the unreachable part. We still want to emit revealed-type + // diagnostics in these sections, so check on the name of the callable here + // and assume that it's actually `typing.reveal_type`. + let is_reveal_type = match func.as_ref() { + ast::Expr::Name(name) => name.id == "reveal_type", + ast::Expr::Attribute(attr) => attr.attr.id == "reveal_type", + _ => false, + }; + if is_reveal_type && let Some(first_arg) = arguments.args.first() { + let revealed_ty = self.expression_type(first_arg); + report_revealed_type(&self.context, revealed_ty, first_arg); + } + } _ => {} } }