class Deprecation { has str $.file; # file of the code that is deprecated has str $.type; # type of code (sub/method etc.) that is deprecated has str $.package; # package of code that is deprecated has str $.name; # name of code that is deprecated has str $.alternative; # alternative for code that is deprecated has %.callsites; # places where called (file -> line -> count) has Version $.from; # release version from which deprecated has Version $.removed; # release version when will be removed my %DEPRECATIONS; # where we keep our deprecation info method DEPRECATIONS() is raw is implementation-detail { %DEPRECATIONS } multi method WHICH (Deprecation:D: --> ValueObjAt:D) { my $which := nqp::list_s("Deprecation"); nqp::push_s($which,$!file || ""); nqp::push_s($which,$!type || ""); nqp::push_s($which,$!package || ""); nqp::push_s($which,$!name || ""); nqp::box_s( nqp::join("|",$which), ValueObjAt ) } proto method report (|) {*} multi method report (Deprecation:U:) { return Nil unless %DEPRECATIONS; my $message = "Saw {+%DEPRECATIONS} occurrence{ 's' if +%DEPRECATIONS != 1 } of deprecated code.\n"; $message ~= ("=" x 80) ~ "\n"; for %DEPRECATIONS.sort(*.key)>>.value>>.report -> $r { $message ~= $r; $message ~= ("-" x 80) ~ "\n"; } %DEPRECATIONS = (); # reset for new batches if applicable $message.chop; } multi method report (Deprecation:D:) { my $type = $.type ?? "$.type " !! ""; my $name = $.name ?? "$.name " !! ""; my $package = $.package ?? "(from $.package) " !! ""; my $message = $type ~ $name ~ $package ~ "seen at:\n"; for %.callsites.kv -> $file, $lines { $message ~= " $file, line{ 's' if +$lines > 1 } { $lines.keys.sort(*.Int).join(',') }\n"; if $.from or $.removed { $message ~= $.from ?? "Deprecated since v$.from, will be removed" !! "Will be removed"; $message ~= $.removed ?? " with release v$.removed!\n" !! " sometime in the future\n"; } } $message ~= "Please use $.alternative instead.\n"; $message; } } class Rakudo::Deprecations { my %DEPRECATIONS := Deprecation.DEPRECATIONS; my $ver; method DEPRECATED( $alternative, $from?, $removed?, :$up = 1, :$what, :$file, :$line, Bool :$lang-vers ) is implementation-detail { $ver //= $*RAKU.compiler.version; my $version = $lang-vers ?? nqp::getcomp('Raku').language_version !! $ver; # if $lang-vers was given, treat the provided versions as language # versions, rather than compiler versions. Note that we can't # `state` the lang version (I think) because different CompUnits # might be using different versions. my Version $vfrom; my Version $vremoved; $from && nqp::iseq_i($version cmp ($vfrom = Version.new: $from), -1) && return; # not deprecated yet; $vremoved = Version.new($removed) if $removed; my $bt = Backtrace.new; my $deprecated = #?if !js $bt[ my $index = $bt.next-interesting-index(1, :named, :setting, :reveal) // 0 ]; #?endif #?if js $bt[ my $index = $bt.next-interesting-index(2, :named, :setting, :reveal) // 0 ]; #?endif if $up ~~ Whatever { $index = $_ with $bt.next-interesting-index($index, :noproto); } else { for ^$up -> $level { $index = $_ with $bt.next-interesting-index($index, :noproto, :setting) } } my $callsite = $bt[$index]; # get object, existing or new my $dep = $what ?? Deprecation.new( :name($what), :$alternative, :from($vfrom), :removed($vremoved) ) !! Deprecation.new( file => $deprecated.file, type => $deprecated.subtype.tc, package => try { $deprecated.package.^name } // 'unknown', name => $deprecated.subname, :$alternative, :from($vfrom), :removed($vremoved), ); $dep = %DEPRECATIONS{$dep.WHICH} //= $dep; state $fatal = %*ENV; die $dep.report if $fatal; # update callsite ++$dep.callsites{$file // $callsite.file.IO}{$line // $callsite.line}; } } END { unless %*ENV { if Deprecation.report -> $message { note $message; # q:to/TEXT/ doesn't work in settings note 'Please contact the author to have these occurrences of deprecated code adapted, so that this message will disappear!'; } } } sub DEPRECATED(|c) is hidden-from-backtrace is implementation-detail { Rakudo::Deprecations.DEPRECATED(|c) } # vim: expandtab shiftwidth=4