Skip to content
Open
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
13 changes: 13 additions & 0 deletions crates/pyrefly_config/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,14 @@ pub struct ConfigOverrideArgs {
num_args = 0..=1
)]
treat_all_caps_as_final: Option<bool>,
/// Display fully qualified names in `reveal_type` output.
#[arg(
long,
default_missing_value = "true",
require_equals = true,
num_args = 0..=1
)]
reveal_type_qualified_names: Option<bool>,
}

impl ConfigOverrideArgs {
Expand Down Expand Up @@ -450,6 +458,7 @@ impl ConfigOverrideArgs {
spec_compliant_overloads,
legacy_overload_expansion,
treat_all_caps_as_final,
reveal_type_qualified_names,
} = self;

environment.has_overrides()
Expand Down Expand Up @@ -478,6 +487,7 @@ impl ConfigOverrideArgs {
|| spec_compliant_overloads.is_some()
|| legacy_overload_expansion.is_some()
|| treat_all_caps_as_final.is_some()
|| reveal_type_qualified_names.is_some()
}

pub fn validate(&self) -> anyhow::Result<()> {
Expand Down Expand Up @@ -653,6 +663,9 @@ impl ConfigOverrideArgs {
if let Some(x) = &self.treat_all_caps_as_final {
config.root.treat_all_caps_as_final = Some(*x);
}
if let Some(x) = &self.reveal_type_qualified_names {
config.reveal_type_qualified_names = *x;
}
let apply_error_settings = |error_config: &mut ErrorDisplayConfig| {
for error_kind in &self.error {
error_config.set_error_severity(*error_kind, Severity::Error);
Expand Down
9 changes: 9 additions & 0 deletions crates/pyrefly_config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,10 @@ pub struct ConfigFile {
#[serde(skip)]
pub search_path_from_args: Vec<PathBuf>,

/// Set only by the `--reveal-type-qualified-names` command-line flag.
#[serde(skip)]
pub reveal_type_qualified_names: bool,

/// The list of directories where imports are
/// imported from, including type checked files.
/// Does not include command-line overrides or the import root!
Expand Down Expand Up @@ -799,6 +803,7 @@ impl Default for ConfigFile {
skip_interpreter_query: false,
},
search_path_from_args: Vec::new(),
reveal_type_qualified_names: false,
search_path_from_file: Vec::new(),
disable_search_path_heuristics: false,
enable_fallback_search_path: false,
Expand Down Expand Up @@ -2566,6 +2571,7 @@ mod tests {
.unwrap(),
project_excludes: Globs::new(vec!["tests/untyped/**".to_owned()]).unwrap(),
search_path_from_args: Vec::new(),
reveal_type_qualified_names: false,
search_path_from_file: vec![PathBuf::from("../..")],
disable_search_path_heuristics: false,
enable_fallback_search_path: false,
Expand Down Expand Up @@ -2976,6 +2982,7 @@ mod tests {
.unwrap(),
project_excludes: Globs::new(vec!["tests/untyped/**".to_owned()]).unwrap(),
search_path_from_args: Vec::new(),
reveal_type_qualified_names: false,
search_path_from_file: vec![PathBuf::from("../..")],
disable_search_path_heuristics: false,
enable_fallback_search_path: false,
Expand Down Expand Up @@ -3065,6 +3072,7 @@ mod tests {
skip_interpreter_query: false,
},
search_path_from_args: Vec::new(),
reveal_type_qualified_names: false,
search_path_from_file: search_path,
disable_search_path_heuristics: false,
enable_fallback_search_path: false,
Expand Down Expand Up @@ -5309,6 +5317,7 @@ bad-override = "error"

let mut config = ConfigFile {
search_path_from_args: vec![mylib_dir.clone()],
reveal_type_qualified_names: false,
import_root: Some(root.path().to_path_buf()),
interpreters: Interpreters {
skip_interpreter_query: true,
Expand Down
44 changes: 32 additions & 12 deletions crates/pyrefly_types/src/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ use pyrefly_derive::TypeEq;
use pyrefly_derive::Visit;
use pyrefly_derive::VisitMut;
use pyrefly_util::assert_bytes;
use pyrefly_util::display::Fmt;
use pyrefly_util::visit::Visit;
use pyrefly_util::visit::VisitMut;
use ruff_python_ast::name::Name;
use starlark_map::small_map::SmallMap;
use starlark_map::smallmap;
use vec1::Vec1;

use crate::display::TypeDisplayContext;
use crate::facet::FacetKind;
use crate::type_var::TypeVar;
use crate::types::AnyStyle;
Expand Down Expand Up @@ -364,20 +366,34 @@ impl TypeInfo {
})
.collect()
}
}

impl Display for TypeInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.ty().fmt(f)?;
/// `ctx` must have been created from every type in this `TypeInfo`.
pub fn display_with<'a>(&'a self, ctx: &'a TypeDisplayContext<'a>) -> impl Display + 'a {
Fmt(move |f| self.fmt_with(&|ty, f| write!(f, "{}", ctx.display(ty)), f))
}

fn fmt_with(&self, fmt_ty: &FmtType, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_ty(self.ty(), f)?;
if let Some(facets) = &self.facets
&& facets.has_displayable_content()
{
write!(f, " ({facets})")?;
write!(f, " (")?;
facets.fmt_with_prefix(&mut Vec::new(), fmt_ty, f)?;
write!(f, ")")?;
}
Ok(())
}
}

impl Display for TypeInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_with(&|ty, f| ty.fmt(f), f)
}
}

/// Writes a single type.
type FmtType<'a> = dyn Fn(&Type, &mut fmt::Formatter<'_>) -> fmt::Result + 'a;

/// Limit on the size of [NarrowedFacets].
///
/// In order to avoid O(n^2) performance of things like x.a = 1; x.b = 2 ....
Expand Down Expand Up @@ -576,6 +592,7 @@ impl NarrowedFacets {
fn fmt_with_prefix(
&self,
prefix: &mut Vec<FacetKind>,
fmt_ty: &FmtType,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let mut first = true;
Expand All @@ -591,17 +608,17 @@ impl NarrowedFacets {
write!(f, ", ")?;
}
match value {
NarrowedFacet::Leaf(ty) => Self::fmt_type_with_label(prefix, facet, ty, f),
NarrowedFacet::Leaf(ty) => Self::fmt_type_with_label(prefix, facet, ty, fmt_ty, f),
NarrowedFacet::WithRoot(ty, facets) => {
Self::fmt_type_with_label(prefix, facet, ty, f)?;
Self::fmt_type_with_label(prefix, facet, ty, fmt_ty, f)?;
if facets.has_displayable_content() {
write!(f, ", ")?;
facets.fmt_with_prefix_and_facet(prefix, facet, f)?;
facets.fmt_with_prefix_and_facet(prefix, facet, fmt_ty, f)?;
}
Ok(())
}
NarrowedFacet::WithoutRoot { facets, .. } => {
facets.fmt_with_prefix_and_facet(prefix, facet, f)
facets.fmt_with_prefix_and_facet(prefix, facet, fmt_ty, f)
}
}?;
}
Expand All @@ -612,10 +629,11 @@ impl NarrowedFacets {
&self,
prefix: &mut Vec<FacetKind>,
facet: &'a FacetKind,
fmt_ty: &FmtType,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
prefix.push(facet.clone());
self.fmt_with_prefix(prefix, f)?;
self.fmt_with_prefix(prefix, fmt_ty, f)?;
prefix.pop();
Ok(())
}
Expand All @@ -624,9 +642,11 @@ impl NarrowedFacets {
prefix: &[FacetKind],
facet: &FacetKind,
ty: &Type,
fmt_ty: &FmtType,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
write!(f, "_{}{}: {}", prefix.iter().join(""), facet, ty)
write!(f, "_{}{}: ", prefix.iter().join(""), facet)?;
fmt_ty(ty, f)
}
}

Expand All @@ -650,7 +670,7 @@ impl VisitMut<Type> for NarrowedFacets {

impl Display for NarrowedFacets {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_with_prefix(&mut Vec::new(), f)
self.fmt_with_prefix(&mut Vec::new(), &|ty, f| ty.fmt(f), f)
}
}

Expand Down
17 changes: 11 additions & 6 deletions pyrefly/lib/alt/special_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/

use pyrefly_python::dunder;
use pyrefly_types::display::TypeDisplayContext;
use pyrefly_types::shaped_array::IntTuple;
use pyrefly_types::shaped_array::IntTupleView;
use pyrefly_types::shaped_array::is_tuple_carrier_shape_middle;
Expand Down Expand Up @@ -207,12 +208,16 @@ impl<'ctx, 'answer, Ans: LookupAnswer> AnswersSolver<'ctx, 'answer, Ans> {
type_info.visit_mut(&mut |ty| {
*ty = self.for_display(ty.clone());
});
self.error(
errors,
range,
ErrorKind::RevealType,
format!("revealed type: {type_info}"),
);
let message = if self.solver().config.reveal_type_qualified_names {
let mut types = Vec::new();
type_info.visit(&mut |ty| types.push(ty));
let mut ctx = TypeDisplayContext::new(&types);
ctx.always_display_module_name_except_builtins();
format!("revealed type: {}", type_info.display_with(&ctx))
} else {
format!("revealed type: {type_info}")
};
self.error(errors, range, ErrorKind::RevealType, message);
ret
} else {
self.error(
Expand Down
2 changes: 2 additions & 0 deletions pyrefly/lib/solver/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@ pub struct SolverConfig {
pub strict_partial_subtyping: bool,
pub spec_compliant_overloads: bool,
pub legacy_overload_expansion: bool,
pub reveal_type_qualified_names: bool,
}

#[derive(Debug)]
Expand Down Expand Up @@ -4553,6 +4554,7 @@ mod tests {
strict_partial_subtyping: false,
spec_compliant_overloads: false,
legacy_overload_expansion: false,
reveal_type_qualified_names: false,
});
let uniques = UniqueFactory::new();
let var = Var::new(&uniques);
Expand Down
2 changes: 2 additions & 0 deletions pyrefly/lib/state/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1568,6 +1568,7 @@ impl<'a> Transaction<'a> {
legacy_overload_expansion: config
.legacy_overload_expansion(module_data.handle.path()),
treat_all_caps_as_final: config.treat_all_caps_as_final(module_data.handle.path()),
reveal_type_qualified_names: config.reveal_type_qualified_names,
recursion_limit_config: config.recursion_limit_config(),
pysa_context,
cinderx_enabled: self.data.cinderx_reporter.is_some(),
Expand Down Expand Up @@ -2649,6 +2650,7 @@ impl<'a> Transaction<'a> {
spec_compliant_overloads: config.spec_compliant_overloads(m.handle.path()),
legacy_overload_expansion: config.legacy_overload_expansion(m.handle.path()),
treat_all_caps_as_final: config.treat_all_caps_as_final(m.handle.path()),
reveal_type_qualified_names: config.reveal_type_qualified_names,
recursion_limit_config: config.recursion_limit_config(),
pysa_context: None,
cinderx_enabled: false,
Expand Down
2 changes: 2 additions & 0 deletions pyrefly/lib/state/steps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub struct Context<'a, Lookup> {
pub spec_compliant_overloads: bool,
pub legacy_overload_expansion: bool,
pub treat_all_caps_as_final: bool,
pub reveal_type_qualified_names: bool,
pub recursion_limit_config: Option<RecursionLimitConfig>,
/// Pysa context for building PysaSolutions during the Solutions step.
pub pysa_context: Option<PysaContext<'a>>,
Expand Down Expand Up @@ -523,6 +524,7 @@ impl Step {
strict_partial_subtyping: ctx.strict_partial_subtyping,
spec_compliant_overloads: ctx.spec_compliant_overloads,
legacy_overload_expansion: ctx.legacy_overload_expansion,
reveal_type_qualified_names: ctx.reveal_type_qualified_names,
});
let ast = ast.module();
let enable_index = ctx.require.keep_index();
Expand Down
19 changes: 19 additions & 0 deletions pyrefly/lib/test/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3080,3 +3080,22 @@ def main(a: DataFrame) -> None:
func(s)
"#,
);

testcase!(
test_reveal_type_qualified_names,
TestEnv::one("foo", "class Box[T]:\n attr: object").enable_reveal_type_qualified_names(),
r#"
from typing import reveal_type
import datetime
from foo import Box

class Local: ...

def f(b: Box[datetime.date], local: Local, xs: list[int]) -> None:
reveal_type(b) # E: revealed type: foo.Box[datetime.date]
reveal_type(local) # E: revealed type: main.Local
reveal_type(xs) # E: revealed type: list[int]
if isinstance(b.attr, datetime.date):
reveal_type(b) # E: revealed type: foo.Box[datetime.date] (_.attr: datetime.date)
"#,
);
8 changes: 8 additions & 0 deletions pyrefly/lib/test/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ pub struct TestEnv {
spec_compliant_overloads: bool,
legacy_overload_expansion: bool,
treat_all_caps_as_final: bool,
reveal_type_qualified_names: bool,
type_ignore_unknown_tag_behavior: TypeIgnoreUnknownTagBehavior,
no_any_return_error: bool,
no_any_return_explicit_error: bool,
Expand Down Expand Up @@ -354,6 +355,7 @@ impl TestEnv {
spec_compliant_overloads: false,
legacy_overload_expansion: false,
treat_all_caps_as_final: false,
reveal_type_qualified_names: false,
type_ignore_unknown_tag_behavior: TypeIgnoreUnknownTagBehavior::Suppress,
no_any_return_error: false,
no_any_return_explicit_error: false,
Expand Down Expand Up @@ -642,6 +644,11 @@ impl TestEnv {
self
}

pub fn enable_reveal_type_qualified_names(mut self) -> Self {
self.reveal_type_qualified_names = true;
self
}

pub fn with_type_ignore_unknown_tag_behavior(
mut self,
behavior: TypeIgnoreUnknownTagBehavior,
Expand Down Expand Up @@ -821,6 +828,7 @@ impl TestEnv {
config.root.spec_compliant_overloads = Some(self.spec_compliant_overloads);
config.root.legacy_overload_expansion = Some(self.legacy_overload_expansion);
config.root.treat_all_caps_as_final = Some(self.treat_all_caps_as_final);
config.reveal_type_qualified_names = self.reveal_type_qualified_names;
if !self.replace_imports_with_any.is_empty() {
let modules = self
.replace_imports_with_any
Expand Down
10 changes: 10 additions & 0 deletions website/docs/error-kinds.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,16 @@ Pyrefly uses this diagnostic to communicate the output of the [`reveal_type`](ht

`reveal_type` is a *directive* — it is always shown in CLI output regardless of the [`min-severity`](../configuration#min-severity) threshold, and is never subject to suppression or baseline exclusion. To hide it, set `reveal-type = "ignore"` in the [`errors`](../configuration#errors) table.

Pass `--reveal-type-qualified-names` to display revealed types with their fully qualified names. Builtins are not qualified.

```python
import numpy as np

reveal_type(np.timedelta64(1, "D"))
# default: timedelta64[timedelta]
# with --reveal-type-qualified-names: numpy.timedelta64[datetime.timedelta]
```

## string-as-iterable

Default severity: `ignore`
Expand Down
Loading