Skip to content

Commit

Permalink
Merge pull request #3853 from rakudo/suggest-capitalization-errors
Browse files Browse the repository at this point in the history
Suggest method names without checking for capitalization
  • Loading branch information
lizmat committed Aug 25, 2020
2 parents c1784bd + 3703469 commit 64dfb2e
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 25 deletions.
79 changes: 55 additions & 24 deletions src/core.c/Exception.pm6
Expand Up @@ -153,6 +153,10 @@ my class X::Method::NotFound is Exception {
has $.typename;
has Bool $.private;
has $.addendum;
has $.message is built(False);
has @.suggestions is built(False);
has @.tips is built(False);

# This attribute is an implementation detail. Not to be documented.
has $.in-class-call;

Expand All @@ -162,14 +166,13 @@ my class X::Method::NotFound is Exception {
!! "of type '$.typename'"
}

method message() {
method TWEAK() {
my @message = $.private
?? "No such private method '!$.method' for invocant $.of-type"
!! "No such method '$.method' for invocant $.of-type";

@message.push: $.addendum if $.addendum;

my @tips;
my $indirect-method = $.method.starts-with("!")
?? $.method.substr(1)
!! "";
Expand Down Expand Up @@ -201,62 +204,90 @@ my class X::Method::NotFound is Exception {
}

my $public_suggested = 0;
sub find_public_suggestion($before, $after --> Nil) {
if $before.fc eq $after.fc {
$public_suggested = 1;
%suggestions{$after} = 0; # assume identity
}
else {
my $dist := StrDistance.new(
before => $before.fc,
after => $after.fc
);
if $dist <= $max_length {
$public_suggested = 1;
%suggestions{$after} = $dist;
}
}
}

if nqp::can($!invocant.HOW, 'methods') {
# $!invocant can be a KnowHOW which method `methods` returns a hash, not a list.
my @invocant_methods = $!invocant.^methods(:local).map: { code-name($_) };
my $invocant_methods :=
Set.new: $!invocant.^methods(:local).map: { code-name($_) };

for $!invocant.^methods(:all) -> $method_candidate {
my $method_name = code-name($method_candidate);
my $method_name := code-name($method_candidate);
# GH#1758 do not suggest a submethod from a parent
next if $method_candidate.^name eq 'Submethod' && !@invocant_methods.first($method_name, :k).defined;
my $dist = StrDistance.new(:before($.method), :after(~$method_name));
if $dist <= $max_length {
$public_suggested = 1;
%suggestions{$method_name} = ~$dist;
}
next
if $method_candidate.^name eq 'Submethod' # a submethod
&& !$invocant_methods{$method_name}; # unknown method

find_public_suggestion($.method, $method_name);
}

# handle special unintrospectable cases
for <HOW WHAT WHO> -> $method_name {
find_public_suggestion($.method, $method_name);
}
}

my $private_suggested = 0;
if $.in-class-call && nqp::can($!invocant.HOW, 'private_method_table') {
for $!invocant.^private_method_table.keys -> $method_name {
my $dist = StrDistance.new(:before($.method), :after(~$method_name));
my $dist = StrDistance.new(
before => $.method.fc,
after => $method_name.fc
);
if $dist <= $max_length {
$private_suggested = 1;
%suggestions{"!$method_name"} = ~$dist
%suggestions{"!$method_name"} = $dist
unless $indirect-method eq $method_name;
}
}
}

if $indirect-method && !$.private && $private_suggested {
@tips.push: "Method name starts with '!', did you mean 'self!\"$indirect-method\"()'?";
@!tips.push: "Method name starts with '!', did you mean 'self!\"$indirect-method\"()'?";
}

if +%suggestions == 1 {
@tips.push: "Did you mean '%suggestions.keys()'?";
@!suggestions = %suggestions.sort(*.value).map(*.key).head(4);

if @!suggestions == 1 {
@!tips.push: "Did you mean '@!suggestions[0]'?";
}
elsif +%suggestions > 1 {
@tips.push: "Did you mean any of these: { %suggestions.sort(*.value)>>.key.head(4).map( { "'$_'" } ).join(", ") }?";
elsif @!suggestions {
@!tips.push: "Did you mean any of these: { @!suggestions.map( { "'$_'" } ).join(", ") }?";
}

if !$indirect-method
&&($private_suggested ^^ $public_suggested)
&& ($private_suggested ^^ $.private)
{
@tips.push: "Perhaps a " ~ ($private_suggested ?? "private" !! "public") ~ " method call must be used."
@!tips.push: "Perhaps a " ~ ($private_suggested ?? "private" !! "public") ~ " method call must be used."
}

if +@tips > 1 {
@tips = @tips.map: "\n" ~ ("- " ~ *).naive-word-wrapper(:indent(" "));
if @!tips > 1 {
@!tips = @!tips.map: "\n" ~ ("- " ~ *).naive-word-wrapper(:indent(" "));
@message.push: ($.addendum ?? "Other possible" !! "Possible") ~ " causes are:";
}
elsif @tips {
@message.push: @tips.shift;
elsif @!tips {
@message.push: @!tips.shift;
}

@message[0] ~= "." if +@message > 1;
@message[0] ~= "." if @message > 1;

@message.join(" ").naive-word-wrapper ~ @tips.join
$!message = @message.join(" ").naive-word-wrapper ~ @!tips.join
}
}

Expand Down
3 changes: 2 additions & 1 deletion t/05-messages/01-errors.t
Expand Up @@ -222,7 +222,8 @@ throws-like { Blob.splice }, X::Multi::NoMatch,
'a public method of the same name as the missing private method is suggested';
throws-like q| class RT123078_3 { method !bar { }; method baz { } }; RT123078_3.new.bar |,
X::Method::NotFound,
message => all(/<<"No such method 'bar'" \W/, /<<'RT123078_3'>>/, /\s+ Did \s+ you \s+ mean \s+ "'baz'"/),
message => all(/<<"No such method 'bar'" \W/, /<<'RT123078_3'>>/, /\s+ Did \s+ you \s+ mean/),
suggestions => <Bag baz>,
'a private method of the same name as the public missing method is not suggested for out-of-class call';
throws-like q| <a a b>.uniq |,
X::Method::NotFound,
Expand Down

0 comments on commit 64dfb2e

Please sign in to comment.