Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: af2e23d343b5
Choose a base ref
...
head repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 27faaee09982
Choose a head ref
  • 2 commits
  • 7 files changed
  • 1 contributor

Commits on Apr 20, 2018

  1. Copy the full SHA
    434c920 View commit details
  2. Merge pull request #5974 from asterite/feature/macro-default-value

    Add `MetaVar#defualt_value` and `MetaVar#has_default_value?`
    asterite authored Apr 20, 2018
    Copy the full SHA
    27faaee View commit details
61 changes: 61 additions & 0 deletions spec/compiler/codegen/macro_spec.cr
Original file line number Diff line number Diff line change
@@ -1712,4 +1712,65 @@ describe "Code gen: macro" do
{{ Foo.union_types.size }}
)).to_i.should eq(2)
end

it "gets default value of instance variable" do
run(%(
class Foo
@x = 1
def default
{{@type.instance_vars.first.default_value}}
end
end
Foo.new.default
)).to_i.should eq(1)
end

it "gets default value of instance variable of generic type" do
run(%(
require "prelude"
struct Int32
def self.foo
10
end
end
class Foo(T)
@x : T = T.foo
def default
{{@type.instance_vars.first.default_value}}
end
end
Foo(Int32).new.default
)).to_i.should eq(10)
end

it "determines if variable has default value" do
run(%(
class Foo
@x = 1
@y : Int32
def initialize(@y)
end
def defaults
{
{{ @type.instance_vars.find { |i| i.name == "x" }.has_default_value? }},
{{ @type.instance_vars.find { |i| i.name == "y" }.has_default_value? }},
}
end
end
x, y = Foo.new(2).defaults
a = 0
a += 1 if x
a += 2 if y
a
)).to_i.should eq(1)
end
end
10 changes: 7 additions & 3 deletions spec/compiler/macro/macro_methods_spec.cr
Original file line number Diff line number Diff line change
@@ -1007,15 +1007,19 @@ describe "macro methods" do

describe "metavar methods" do
it "executes nothing" do
assert_macro "x", %({{x}}), [MetaVar.new("foo", Program.new.int32)] of ASTNode, %(foo)
assert_macro "x", %({{x}}), [MetaMacroVar.new("foo", Program.new.int32)] of ASTNode, %(foo)
end

it "executes name" do
assert_macro "x", %({{x.name}}), [MetaVar.new("foo", Program.new.int32)] of ASTNode, %(foo)
assert_macro "x", %({{x.name}}), [MetaMacroVar.new("foo", Program.new.int32)] of ASTNode, %(foo)
end

it "executes id" do
assert_macro "x", %({{x.id}}), [MetaVar.new("foo", Program.new.int32)] of ASTNode, %(foo)
assert_macro "x", %({{x.id}}), [MetaMacroVar.new("foo", Program.new.int32)] of ASTNode, %(foo)
end

it "executes is_a?" do
assert_macro "x", %({{x.is_a?(MetaVar)}}), [MetaMacroVar.new("foo", Program.new.int32)] of ASTNode, %(true)
end
end

13 changes: 13 additions & 0 deletions src/compiler/crystal/macros.cr
Original file line number Diff line number Diff line change
@@ -786,6 +786,19 @@ module Crystal::Macros
# Returns the type of this variable, if known, or `nil`.
def type : TypeNode | NilLiteral
end

# Returns the default value of this variable.
# Note that if the variable doesn't have a default value,
# or the default value is `nil`, a `NilLiteral` will be
# returned. To distinguish between these cases, use
# `has_default_value?`.
def default_value : ASTNode
end

# Returns whether this variable has a default value (which.
# can in turn be `nil`).
def has_default_value? : BoolLiteral
end
end

# A local variable or block argument.
18 changes: 16 additions & 2 deletions src/compiler/crystal/macros/methods.cr
Original file line number Diff line number Diff line change
@@ -1044,7 +1044,7 @@ module Crystal
end
end

class MetaVar < ASTNode
class MetaMacroVar < ASTNode
def to_macro_id
@name
end
@@ -1061,6 +1061,14 @@ module Crystal
NilLiteral.new
end
end
when "default_value"
interpret_argless_method(method, args) do
default_value || NilLiteral.new
end
when "has_default_value?"
interpret_argless_method(method, args) do
BoolLiteral.new(!!default_value)
end
else
super
end
@@ -1572,8 +1580,14 @@ module Crystal

def self.instance_vars(type)
if type.is_a?(InstanceVarContainer)
if type.is_a?(InstanceVarInitializerContainer)
initializers = type.instance_vars_initializers
end

ArrayLiteral.map(type.all_instance_vars) do |name, ivar|
MetaVar.new(name[1..-1], ivar.type)
meta_var = MetaMacroVar.new(name[1..-1], ivar.type)
meta_var.default_value = initializers.try &.find { |init| init.name == name }.try &.value
meta_var
end
else
empty_no_return_array
17 changes: 17 additions & 0 deletions src/compiler/crystal/semantic/ast.cr
Original file line number Diff line number Diff line change
@@ -717,4 +717,21 @@ module Crystal

def_equals_and_hash value
end

# Ficticious node representing a variable in macros
class MetaMacroVar < ASTNode
property name : String
property default_value : ASTNode?

def initialize(@name, @type)
end

def class_desc
"MetaVar"
end

def clone_without_location
self
end
end
end
4 changes: 4 additions & 0 deletions src/compiler/crystal/semantic/to_s.cr
Original file line number Diff line number Diff line change
@@ -35,6 +35,10 @@ module Crystal
@str << node.name
end

def visit(node : MetaMacroVar)
@str << node.name
end

def visit(node : TypeFilteredNode)
false
end
2 changes: 1 addition & 1 deletion src/compiler/crystal/semantic/transformer.cr
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ require "../syntax/transformer"

module Crystal
class Transformer
def transform(node : MetaVar | Primitive | TypeFilteredNode | TupleIndexer | TypeNode | TypeRestriction | YieldBlockBinder | MacroId)
def transform(node : MetaVar | MetaMacroVar | Primitive | TypeFilteredNode | TupleIndexer | TypeNode | TypeRestriction | YieldBlockBinder | MacroId)
node
end