@@ -23,13 +23,14 @@ use std::path::PathBuf;
2323
2424use miri:: Immediate :: Uninit ;
2525use miri:: { interpret, * } ;
26- use rustc_abi:: Size ;
26+ use rustc_abi:: { FIRST_VARIANT , FieldIdx , Size } ;
2727use rustc_driver:: Compilation ;
2828use rustc_hir:: attrs:: CrateType ;
29+ use rustc_hir:: def:: CtorKind ;
2930use rustc_interface:: interface;
3031use rustc_middle:: mir:: interpret:: AllocId ;
3132use rustc_middle:: mir:: { self , Local , ProjectionElem , VarDebugInfoContents , VarDebugInfoFragment } ;
32- use rustc_middle:: ty:: { TyCtxt , TyKind } ;
33+ use rustc_middle:: ty:: { self , TyCtxt , TyKind } ;
3334use rustc_session:: EarlyDiagCtxt ;
3435use rustc_session:: config:: ErrorOutputType ;
3536use rustc_span:: source_map:: SourceMap ;
@@ -505,6 +506,179 @@ impl<'tcx> PrirodaContext<'tcx> {
505506 interp_ok ( format ! ( "[{}]" , rendered. join( " " ) ) )
506507 }
507508
509+ /// Render an evaluated operand using Rust-source-shaped containers with raw leaves.
510+ ///
511+ /// The operand is produced from live interpreter state, usually via `local_to_op`
512+ /// for a whole MIR local or `eval_place_to_op` for a projected debug-info place.
513+ ///
514+ /// This intentionally does not call user `Debug` / `Display`, and it does not
515+ /// try to make every scalar leaf pretty yet. Unsupported cases and leaf values
516+ /// fall back to `render_op`, preserving the old raw byte/provenance renderer.
517+ ///
518+ /// FIXME: teach the leaf renderer about simple Rust scalars (`bool`, integers,
519+ /// chars, raw pointers/references) once the source-shaped container output is
520+ /// stable enough to stop depending on byte dumps for every field.
521+ ///
522+ /// FIXME: decide how much dereferencing belongs in this renderer. References
523+ /// currently stay as raw pointer leaves; following them may belong in the
524+ /// existing `follow` command instead of automatic local rendering.
525+ fn render_source_shaped_op ( & self , op : OpTy < ' tcx > ) -> String {
526+ self . render_source_shaped_op_inner ( op, 0 )
527+ }
528+
529+ /// Recursive worker for `render_source_shaped_op`.
530+ ///
531+ /// The depth limit keeps cyclic/reference-heavy values from making debugger
532+ /// output explode once more container kinds are added. At the limit, the raw
533+ /// renderer remains the ground truth.
534+ ///
535+ /// FIXME: replace this fixed recursion limit with a value-size/output-budget
536+ /// policy so large acyclic values and deeply nested values degrade more
537+ /// predictably.
538+ fn render_source_shaped_op_inner ( & self , op : OpTy < ' tcx > , depth : usize ) -> String {
539+ const MAX_SOURCE_SHAPE_DEPTH : usize = 8 ;
540+
541+ if depth >= MAX_SOURCE_SHAPE_DEPTH {
542+ return self . render_op ( op) ;
543+ }
544+
545+ match op. layout . ty . kind ( ) {
546+ // Empty enums have no active variant to format. Unions do not record
547+ // which field is currently active, so choosing one would be misleading.
548+ //
549+ // FIXME: support unions only with an explicit user-selected field or
550+ // another source of active-field information. Guessing from layout
551+ // bytes would make debugger output look more certain than it is.
552+ ty:: Adt ( def, _) if def. variants ( ) . is_empty ( ) || def. is_union ( ) => self . render_op ( op) ,
553+
554+ ty:: Adt ( def, _) => {
555+ // Enums need their runtime discriminant and a downcasted layout
556+ // view before fields can be projected. Structs use their sole
557+ // variant directly. Keep the display name tied to the same choice.
558+ let ( variant_idx, down, name) = if def. is_enum ( ) {
559+ let variant_idx = match self . ecx . read_discriminant ( & op) . discard_err ( ) {
560+ Some ( variant_idx) => variant_idx,
561+ // FIXME: expose this as an explicit render error when
562+ // Priroda grows structured value states. Falling back to
563+ // bytes keeps today's UI usable but hides why the enum
564+ // could not be source-shaped.
565+ None => return self . render_op ( op) ,
566+ } ;
567+ let down = match self . ecx . project_downcast ( & op, variant_idx) . discard_err ( ) {
568+ Some ( down) => down,
569+ // FIXME: distinguish invalid/uninitialized discriminants
570+ // from projection bugs in the rendered output once locals
571+ // can carry structured diagnostics.
572+ None => return self . render_op ( op) ,
573+ } ;
574+ let variant_def = & def. variants ( ) [ variant_idx] ;
575+ (
576+ variant_idx,
577+ down,
578+ format ! ( "{}::{}" , self . ecx. tcx. item_name( def. did( ) ) , variant_def. name) ,
579+ )
580+ } else {
581+ let variant_idx = FIRST_VARIANT ;
582+ let variant_def = & def. variants ( ) [ variant_idx] ;
583+ ( variant_idx, op. clone ( ) , variant_def. name . to_string ( ) )
584+ } ;
585+
586+ let variant_def = & def. variants ( ) [ variant_idx] ;
587+
588+ let mut fields = Vec :: with_capacity ( variant_def. fields . len ( ) ) ;
589+ for i in 0 ..variant_def. fields . len ( ) {
590+ let field_idx = FieldIdx :: from_usize ( i) ;
591+ // `project_field` avoids manual offset math and works for both
592+ // immediate and memory-backed operands through `Projectable`.
593+ let field_op = match self . ecx . project_field ( & down, field_idx) . discard_err ( ) {
594+ Some ( field_op) => field_op,
595+ // FIXME: preserve the successfully rendered fields and
596+ // mark only this field as unavailable once the value model
597+ // can represent partial render failures.
598+ None => return self . render_op ( op) ,
599+ } ;
600+ fields. push ( self . render_source_shaped_op_inner ( field_op, depth + 1 ) ) ;
601+ }
602+
603+ // Match Rust constructor spelling:
604+ // - `Const`: unit structs/variants, e.g. `UnitStruct`, `Enum::Unit`
605+ // - `Fn`: tuple structs/variants, e.g. `Pair(a, b)` or `EmptyTuple()`
606+ // - `None`: braced structs/variants, including the empty `{}` case
607+ match variant_def. ctor_kind ( ) {
608+ Some ( CtorKind :: Const ) => name,
609+ Some ( CtorKind :: Fn ) => format ! ( "{name}({})" , fields. join( ", " ) ) ,
610+ None if fields. is_empty ( ) => format ! ( "{name} {{}}" ) ,
611+ None => {
612+ let fields = variant_def
613+ . fields
614+ . iter ( )
615+ . zip ( fields)
616+ . map ( |( field_def, value) | format ! ( "{}: {value}" , field_def. name) )
617+ . collect :: < Vec < _ > > ( )
618+ . join ( ", " ) ;
619+ format ! ( "{name} {{ {fields} }}" )
620+ }
621+ }
622+ }
623+
624+ ty:: Tuple ( args) => {
625+ let mut fields = Vec :: with_capacity ( args. len ( ) ) ;
626+ for i in 0 ..args. len ( ) {
627+ // Tuples have no field names in source, so preserve their
628+ // source field order and render children positionally.
629+ let field_op =
630+ match self . ecx . project_field ( & op, FieldIdx :: from_usize ( i) ) . discard_err ( ) {
631+ Some ( field_op) => field_op,
632+ // FIXME: render tuple fields independently so one
633+ // projection failure does not throw away the whole
634+ // source-shaped tuple.
635+ None => return self . render_op ( op) ,
636+ } ;
637+ fields. push ( self . render_source_shaped_op_inner ( field_op, depth + 1 ) ) ;
638+ }
639+
640+ if fields. len ( ) == 1 {
641+ format ! ( "({},)" , fields[ 0 ] )
642+ } else {
643+ format ! ( "({})" , fields. join( ", " ) )
644+ }
645+ }
646+
647+ ty:: Array ( _, _) | ty:: Slice ( _) => {
648+ // `project_array_fields` uses the dynamic length for slices. That
649+ // avoids the classic mistake of treating slice layout as a fixed
650+ // zero-length array.
651+ let mut iter = match self . ecx . project_array_fields ( & op) . discard_err ( ) {
652+ Some ( iter) => iter,
653+ // FIXME: when slice metadata is invalid, show that as a slice
654+ // length problem instead of silently falling back to raw bytes.
655+ None => return self . render_op ( op) ,
656+ } ;
657+
658+ let mut fields = Vec :: new ( ) ;
659+ // FIXME: add an output budget/truncation policy before rendering
660+ // very large arrays or slices in full.
661+ loop {
662+ match iter. next ( & self . ecx ) . discard_err ( ) {
663+ Some ( Some ( ( _idx, field_op) ) ) =>
664+ fields. push ( self . render_source_shaped_op_inner ( field_op, depth + 1 ) ) ,
665+ Some ( None ) => break ,
666+ // FIXME: keep already-rendered elements and mark the
667+ // failed index once partial render errors are supported.
668+ None => return self . render_op ( op) ,
669+ }
670+ }
671+
672+ format ! ( "[{}]" , fields. join( ", " ) )
673+ }
674+
675+ // FIXME: consider source-shaped special cases for strings, closures,
676+ // generators/coroutines, trait objects, and SIMD/vector-like types.
677+ // Until then these stay on the raw renderer path.
678+ _ => self . render_op ( op) ,
679+ }
680+ }
681+
508682 /// Render an evaluated operand using the same raw representation for
509683 /// whole locals and projected MIR places.
510684 fn render_op ( & self , op : OpTy < ' tcx > ) -> String {
@@ -613,7 +787,7 @@ impl<'tcx> PrirodaContext<'tcx> {
613787 . ecx
614788 . local_to_op ( local, None )
615789 . expect ( "this error can only occur in CTFE on generic code" ) ;
616- local_desc. value = self . render_op ( op) ;
790+ local_desc. value = self . render_source_shaped_op ( op) ;
617791 }
618792 } ;
619793
@@ -682,7 +856,7 @@ impl<'tcx> PrirodaContext<'tcx> {
682856 let value = self
683857 . ecx
684858 . eval_place_to_op ( * place, None )
685- . map ( |op| self . render_op ( op) )
859+ . map ( |op| self . render_source_shaped_op ( op) )
686860 . unwrap_or_else ( |err| {
687861 format ! ( "<error: {}>" , interpret:: format_interp_error( err) )
688862 } ) ;
0 commit comments