Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check that def, macro, and block parameters don't end with ? or ! #12197

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

potomak
Copy link
Contributor

@potomak potomak commented Jul 4, 2022

Fixes #10917

Tested with:

$ crystal spec/compiler/parser/parser_spec.cr
[...]
Finished in 212.78 milliseconds
2656 examples, 0 failures, 0 errors, 0 pending

@potomak
Copy link
Contributor Author

potomak commented Jul 4, 2022

Note: this is a breaking change.

I decided to raise an exception because I didn't find how to log a warning + test warning message. In parser_spec.cr I just found it_parses and assert_syntax_error.

What do you think of this approach?

In case you suggest to keep this change backward compatible can you point me to an example of warning + test for the parser please?

@potomak potomak changed the title Check that def, macro, and block parameters don't end with ? or ! Check that def, macro, and block parameters don't end with ? or ! Jul 9, 2022
@straight-shoota
Copy link
Member

straight-shoota commented Jul 15, 2022

This should not be a breaking change. So we'll need a warning.

You can use @program.report_warning or .report_warning_at for that

def report_warning(node : ASTNode, message : String)
return unless self.warnings.all?
return if self.ignore_warning_due_to_location?(node.location)
self.warning_failures << node.warning(message)
end
def report_warning_at(location : Location?, message : String)
return unless self.warnings.all?
return if self.ignore_warning_due_to_location?(location)
if location
message = String.build do |io|
exception = SyntaxException.new message, location.line_number, location.column_number, location.filename
exception.warning = true
exception.append_to_s(io, nil)
end
end
self.warning_failures << message
end

There are currently no warnings in the parser and no specs for that.
You should be able to use the assert_warning spec helper. Or adapt it to a variant that does only syntax parsing and skip the semantic stage. But feel free to leave that out.

@HertzDevil
Copy link
Contributor

HertzDevil commented Jul 15, 2022

As I mentioned somewhere else (in the Int128 literals issue I think), the parser does not have access to the Crystal::Program because it doesn't need one, so the warnings have to be collected through some other means.

@straight-shoota
Copy link
Member

Ah, okay. Then we'll probably need a bigger change in order to move forward with this.
If you want to investigate this, @potomak, please go ahead. But don't hesitate to drop this for now.

@potomak
Copy link
Contributor Author

potomak commented Jul 17, 2022

@straight-shoota would it make sense if I add @program to Parser and then use report_warning instead of raising and exception?

I guess it would be better to split the work in two PRs, one for updating the definition of Parser and an update of this one on top of that.

Any objections?

@HertzDevil
Copy link
Contributor

I think it should be the opposite: the parser itself should be able to report warnings even without a Crystal::Program.

@straight-shoota
Copy link
Member

Yeah, I suppose it would be preferable to collect warnings for an individual parser run vs. amending everything in Program. The reason for that is that there are some locations where individual parser instances are used during the compiler stages (for example for (re-)parsing macro code or in the interpreter REPL). It would be good if we could easily identify warnings in a particular parser run instead of having all merged together in the Program's warnings.

@HertzDevil
Copy link
Contributor

@potomak are you interested in turning those checks into syntax warnings similar to #12427?

@potomak
Copy link
Contributor Author

potomak commented Sep 17, 2022

@potomak are you interested in turning those checks into syntax warnings similar to #12427?

@HertzDevil yes and sorry for the late reply.

Comment on lines 4040 to 4042
if param_name.ends_with?(/\?|!/)
raise "invalid param name"
end
Copy link
Contributor

@Sija Sija Sep 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small optimization (no regex) + rephrased error message to make it more clear about the name in question:

Suggested change
if param_name.ends_with?(/\?|!/)
raise "invalid param name"
end
if param_name[-1]?.in?('?', '!')
raise "invalid parameter name: #{param_name}"
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I use this version I get a bunch of Index out of bounds (IndexError) errors.

I guess I should first check if the param_name string is not empty, what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can go with this:

Suggested change
if param_name.ends_with?(/\?|!/)
raise "invalid param name"
end
if param_name.ends_with?("?") || param_name.ends_with?("!")
raise "invalid param name"
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've edited the suggestion

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so the choice is between:

param_name[-1]?.in?('?', '!')
!param_name.empty? && param_name[-1].in?('?', '!')
param_name.ends_with?("?") || param_name.ends_with?("!")

I think the last one is the most clear.

Is there an advantage in using version 1 or 2?
Which one is the most idiomatic?

I've turned failures to parse params with invalid names into syntax
warnings similar to crystal-lang#12427, as suggested by @HertzDevil.
Comment on lines +3991 to +3993
if external_name_token.nil?
raise "missing external name token"
end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When external_name is present external_name_token should also be present, so it may be better to turn this into an invariant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Def, macro, and block parameters could end with ? or !
5 participants