@@ -557,6 +557,230 @@ def batch(files, output)
557557 end
558558 output . puts dump_declarations ( file )
559559 end
560+
561+ if @options [ :output_stats ]
562+ rb_files = show_files . reject { |f | File . extname ( f ) == ".rbs" }
563+ stats = collect_stats ( rb_files )
564+ output . puts
565+ output . puts format_stats ( stats )
566+ end
567+ end
568+
569+ def collect_stats ( files )
570+ file_stats = [ ]
571+
572+ files . each do |path |
573+ methods = [ ]
574+ constants = [ ]
575+ seen_ivars = Set [ ]
576+ ivars = [ ]
577+ seen_cvars = Set [ ]
578+ cvars = [ ]
579+ seen_gvars = Set [ ]
580+ gvars = [ ]
581+
582+ @rb_text_nodes [ path ] &.traverse do |event , node |
583+ next unless event == :enter
584+
585+ node . boxes ( :mdef ) do |mdef |
586+ param_slots = [ ]
587+ f = mdef . f_args
588+ [ f . req_positionals , f . opt_positionals , f . post_positionals , f . req_keywords , f . opt_keywords ] . each do |ary |
589+ ary . each { |vtx | param_slots << classify_vertex ( vtx ) }
590+ end
591+ [ f . rest_positionals , f . rest_keywords ] . each do |vtx |
592+ param_slots << classify_vertex ( vtx ) if vtx
593+ end
594+
595+ is_initialize = mdef . mid == :initialize
596+ ret_slots = is_initialize ? [ ] : [ classify_vertex ( mdef . ret ) ]
597+
598+ blk = mdef . record_block
599+ block_param_slots = [ ]
600+ block_ret_slots = [ ]
601+ if blk . used
602+ blk . f_args . each { |vtx | block_param_slots << classify_vertex ( vtx ) }
603+ block_ret_slots << classify_vertex ( blk . ret )
604+ end
605+
606+ methods << {
607+ mid : mdef . mid ,
608+ singleton : mdef . singleton ,
609+ param_slots : param_slots ,
610+ ret_slots : ret_slots ,
611+ block_param_slots : block_param_slots ,
612+ block_ret_slots : block_ret_slots ,
613+ }
614+ end
615+
616+ if node . is_a? ( AST ::ConstantWriteNode ) && node . static_cpath
617+ constants << classify_vertex ( node . ret )
618+ end
619+
620+ if node . is_a? ( AST ::InstanceVariableWriteNode )
621+ scope = node . lenv . cref . scope_level
622+ if scope == :class || scope == :instance
623+ key = [ node . lenv . cref . cpath , scope == :class , node . var ]
624+ unless seen_ivars . include? ( key )
625+ seen_ivars << key
626+ ve = @genv . resolve_ivar ( key [ 0 ] , key [ 1 ] , key [ 2 ] )
627+ ivars << classify_vertex ( ve . vtx )
628+ end
629+ end
630+ end
631+
632+ if node . is_a? ( AST ::ClassVariableWriteNode )
633+ key = [ node . lenv . cref . cpath , node . var ]
634+ unless seen_cvars . include? ( key )
635+ seen_cvars << key
636+ ve = @genv . resolve_cvar ( key [ 0 ] , key [ 1 ] )
637+ cvars << classify_vertex ( ve . vtx )
638+ end
639+ end
640+
641+ if node . is_a? ( AST ::GlobalVariableWriteNode )
642+ unless seen_gvars . include? ( node . var )
643+ seen_gvars << node . var
644+ ve = @genv . resolve_gvar ( node . var )
645+ gvars << classify_vertex ( ve . vtx )
646+ end
647+ end
648+ end
649+
650+ file_stats << {
651+ path : path ,
652+ methods : methods ,
653+ constants : constants ,
654+ ivars : ivars ,
655+ cvars : cvars ,
656+ gvars : gvars ,
657+ }
658+ end
659+
660+ file_stats
661+ end
662+
663+ def classify_vertex ( vtx )
664+ vtx . types . empty? ? :untyped : :typed
665+ end
666+
667+ def format_stats ( stats )
668+ total_methods = 0
669+ fully_typed = 0
670+ partially_typed = 0
671+ fully_untyped = 0
672+
673+ slot_categories = %i[ param ret blk_param blk_ret const ivar cvar gvar ]
674+ typed = Hash . new ( 0 )
675+ untyped = Hash . new ( 0 )
676+
677+ file_summaries = [ ]
678+
679+ stats . each do |file |
680+ f_typed = 0
681+ f_total = 0
682+
683+ file [ :methods ] . each do |m |
684+ total_methods += 1
685+
686+ method_slot_keys = %i[ param_slots ret_slots block_param_slots block_ret_slots ]
687+ category_keys = %i[ param ret blk_param blk_ret ]
688+
689+ all_slots = method_slot_keys . flat_map { |k | m [ k ] }
690+
691+ method_slot_keys . zip ( category_keys ) do |slot_key , cat |
692+ m [ slot_key ] . each do |s |
693+ if s == :typed
694+ typed [ cat ] += 1
695+ else
696+ untyped [ cat ] += 1
697+ end
698+ end
699+ end
700+
701+ if all_slots . empty? || all_slots . all? { |s | s == :typed }
702+ fully_typed += 1
703+ elsif all_slots . none? { |s | s == :typed }
704+ fully_untyped += 1
705+ else
706+ partially_typed += 1
707+ end
708+
709+ f_typed += all_slots . count ( :typed )
710+ f_total += all_slots . size
711+ end
712+
713+ %i[ constants ivars cvars gvars ] . zip ( %i[ const ivar cvar gvar ] ) do |data_key , cat |
714+ file [ data_key ] . each do |s |
715+ f_total += 1
716+ if s == :typed
717+ typed [ cat ] += 1
718+ f_typed += 1
719+ else
720+ untyped [ cat ] += 1
721+ end
722+ end
723+ end
724+
725+ if f_total > 0
726+ file_summaries << {
727+ path : file [ :path ] ,
728+ methods : file [ :methods ] . size ,
729+ typed : f_typed ,
730+ total : f_total ,
731+ }
732+ end
733+ end
734+
735+ overall_typed = slot_categories . sum { |c | typed [ c ] }
736+ overall_untyped = slot_categories . sum { |c | untyped [ c ] }
737+ overall_total = overall_typed + overall_untyped
738+
739+ labels = {
740+ param : "Parameter slots" ,
741+ ret : "Return slots" ,
742+ blk_param : "Block parameter slots" ,
743+ blk_ret : "Block return slots" ,
744+ const : "Constants" ,
745+ ivar : "Instance variables" ,
746+ cvar : "Class variables" ,
747+ gvar : "Global variables" ,
748+ }
749+
750+ lines = [ ]
751+ lines << "# TypeProf Evaluation Statistics"
752+ lines << "#"
753+ lines << "# Total methods: #{ total_methods } "
754+ lines << "# Fully typed: #{ fully_typed } "
755+ lines << "# Partially typed: #{ partially_typed } "
756+ lines << "# Fully untyped: #{ fully_untyped } "
757+
758+ slot_categories . each do |cat |
759+ total = typed [ cat ] + untyped [ cat ]
760+ lines << "#"
761+ lines << "# #{ labels [ cat ] } : #{ total } "
762+ lines << "# Typed: #{ typed [ cat ] } (#{ pct ( typed [ cat ] , total ) } )"
763+ lines << "# Untyped: #{ untyped [ cat ] } (#{ pct ( untyped [ cat ] , total ) } )"
764+ end
765+
766+ lines << "#"
767+ lines << "# Overall: #{ overall_typed } /#{ overall_total } typed (#{ pct ( overall_typed , overall_total ) } )"
768+ lines << "# #{ overall_untyped } /#{ overall_total } untyped (#{ pct ( overall_untyped , overall_total ) } )"
769+
770+ if file_summaries . size > 1
771+ lines << "#"
772+ lines << "# Per-file breakdown:"
773+ file_summaries . each do |fs |
774+ lines << "# #{ fs [ :path ] } : #{ fs [ :methods ] } methods, #{ fs [ :typed ] } /#{ fs [ :total ] } typed (#{ pct ( fs [ :typed ] , fs [ :total ] ) } )"
775+ end
776+ end
777+
778+ lines . join ( "\n " )
779+ end
780+
781+ def pct ( n , total )
782+ return "0.0%" if total == 0
783+ "#{ ( n * 100.0 / total ) . round ( 1 ) } %"
560784 end
561785
562786 private
0 commit comments