Explode method expansion

Somewhere along the way the block argument gets dropped.
Even though `method` is a `Def` that accepts a block, the `&block` portion is dropped.
Possible Crystal compiler bug.
This commit is contained in:
Michael Miller 2022-03-19 16:13:11 -06:00
parent 15dd2ea6f1
commit 17e97cb39a
No known key found for this signature in database
GPG key ID: 32B47AE8F388A1FF
2 changed files with 29 additions and 4 deletions

View file

@ -5,7 +5,7 @@ Spectator.describe Spectator::Double do
Spectator::Double.define(FooBarDouble, "dbl-name", foo: 42, bar: "baz") Spectator::Double.define(FooBarDouble, "dbl-name", foo: 42, bar: "baz")
# The subject `dbl` must be carefully used in sub-contexts, otherwise it pollutes parent scopes. # The subject `dbl` must be carefully used in sub-contexts, otherwise it pollutes parent scopes.
# This changes the type of `dbl` to `Double`, which produces a union of methods and their return types. # This changes the type of `dbl` to `Double+`, which produces a union of methods and their return types.
context "plain double" do context "plain double" do
subject(dbl) { FooBarDouble.new } subject(dbl) { FooBarDouble.new }
@ -38,6 +38,10 @@ Spectator.describe Spectator::Double do
it "uses nil for undefined messages" do it "uses nil for undefined messages" do
expect { dbl.baz }.to compile_as(Nil) expect { dbl.baz }.to compile_as(Nil)
end end
it "supports blocks" do
dbl.bar
end
end end
context "without a double name" do context "without a double name" do

View file

@ -269,12 +269,33 @@ module Spectator
end end
# Utility for defining a stubbed method and a fallback. # Utility for defining a stubbed method and a fallback.
#
# NOTE: The method definition is exploded and redefined by its parts because using `{{method}}` omits the block argument.
private macro inject_stub(method) private macro inject_stub(method)
{% if method.abstract? %} {% if method.abstract? %}
abstract_stub {{method}} abstract_stub {% if method.visibility != :public %}{{method.visibility.id}}{% end %} abstract def {{method.receiver}}{{method.name}}(
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %}
{% if method.double_splat %}**{{method.double_splat}}, {% end %}
{% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %}
){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %}
{% else %} {% else %}
{{method}} {% if method.visibility != :public %}{{method.visibility.id}}{% end %} def {{method.receiver}}{{method.name}}(
stub {{method}} {% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %}
{% if method.double_splat %}**{{method.double_splat}}, {% end %}
{% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %}
){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %}
{{method.body}}
end
stub {% if method.visibility != :public %}{{method.visibility.id}}{% end %} def {{method.receiver}}{{method.name}}(
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %}
{% if method.double_splat %}**{{method.double_splat}}, {% end %}
{% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %}
){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %}
# Content of this method is discarded,
# but this will compile successfully even if it's used.
previous_def{% if method.accepts_block? %} { |*%yargs| yield *%yargs }{% end %}
end
{% end %} {% end %}
end end