@@ 15,24 15,73 @@ module Dhall
::TrueClass => "Bool"
}.freeze
- def self.tag_for(o, type)
+ def self.tag_for(o)
return "Natural" if o.is_a?(::Integer) && !o.negative?
TAGS.fetch(o.class) do
- "#{o.class.name}_#{type.digest.hexdigest}"
+ o.class.name
end
end
- def self.union_of(values_and_types)
- values_and_types.reduce([UnionType.new, []]) do |(ut, tags), (v, t)|
- tag = tag_for(v, t)
- if t.is_a?(UnionType) && t.alternatives.length == 1
- tag, t = t.alternatives.to_a.first
+ class AnnotatedExpressionList
+ attr_reader :type
+ attr_reader :exprs
+
+ def self.from(type_annotation)
+ if type_annotation.nil?
+ new(nil, [nil])
+ else
+ new(type_annotation.type, [type_annotation.value])
end
- [
- ut.merge(UnionType.new(alternatives: { tag => t })),
- tags + [tag]
- ]
+ end
+
+ def initialize(type, exprs)
+ @type = type
+ @exprs = exprs
+ end
+
+ def +(other)
+ raise "#{type} != #{other.type}" if type != other.type
+ self.class.new(type, exprs + other.exprs)
+ end
+ end
+
+ class UnionInferer
+ def initialize(tagged={})
+ @tagged = tagged
+ end
+
+ def union_type
+ UnionType.new(alternatives: Hash[@tagged.map { |k, v| [k, v.type] }])
+ end
+
+ def union_for(expr)
+ if expr.is_a?(Enum)
+ tag = expr.tag
+ expr = nil
+ else
+ tag = @tagged.keys.find { |k| @tagged[k].exprs.include?(expr) }
+ end
+ expr = expr.extract if expr.is_a?(Union)
+ Union.from(union_type, tag, expr)
+ end
+
+ def with(tag, type_annotation)
+ anno = AnnotatedExpressionList.from(type_annotation)
+ if @tagged.key?(tag) && @tagged[tag].type != anno.type
+ disambiguate_against(tag, anno)
+ else
+ self.class.new(@tagged.merge(tag => anno) { |_, x, y| x + y })
+ end
+ end
+
+ def disambiguate_against(tag, anno)
+ self.class.new(
+ @tagged.reject { |k, _| k == tag }.merge(
+ "#{tag}_#{@tagged[tag].type.digest.hexdigest}" => @tagged[tag],
+ "#{tag}_#{anno.type.digest.hexdigest}" => anno
+ )
+ )
end
end
@@ 144,21 193,28 @@ module Dhall
class Union
def initialize(values, exprs, types)
- @values = values
+ @tags, @types = values.zip(types).map { |(value, type)|
+ if type.is_a?(UnionType) && type.alternatives.length == 1
+ type.alternatives.to_a.first
+ else
+ [AsDhall.tag_for(value), type]
+ end
+ }.transpose
@exprs = exprs
- @types = types
+ @inferer = UnionInferer.new
end
def list
- ut, tags = AsDhall.union_of(@values.zip(@types))
-
- List.new(elements: @exprs.zip(tags).map do |(expr, tag)|
- if expr.is_a?(Dhall::Union) && expr.alternatives.empty?
- expr = expr.is_a?(Dhall::Enum) ? nil : expr.extract
+ final_inferer =
+ @tags
+ .zip(@exprs, @types)
+ .reduce(@inferer) do |inferer, (tag, expr, type)|
+ inferer.with(
+ tag,
+ type.nil? ? nil : TypeAnnotation.new(value: expr, type: type)
+ )
end
-
- Dhall::Union.from(ut, tag, expr)
- end)
+ List.new(elements: @exprs.map(&final_inferer.method(:union_for)))
end
end
end
@@ 825,9 825,9 @@ module Dhall
alternatives.fetch(k)
end
- def without(key)
- key = key.to_s
- with(alternatives: alternatives.reject { |k, _| k == key })
+ def without(*keys)
+ keys.map!(&:to_s)
+ with(alternatives: alternatives.reject { |k, _| keys.include?(k) })
end
def record
@@ 842,8 842,8 @@ module Dhall
self == other
end
- def merge(other)
- with(alternatives: alternatives.merge(other.alternatives))
+ def merge(other, &block)
+ with(alternatives: alternatives.merge(other.alternatives, &block))
end
def fetch(k, default=nil)
@@ 112,48 112,87 @@ class TestAsDhall < Minitest::Test
)
end
+ class SomeTestClass
+ def initialize(a: 1, b: "hai")
+ @a = a
+ @b = b
+ end
+ end
+
def test_array_mixed
- array_key = "Array_f256441295d38d19e84f2de0596f5ae2377" \
- "c923c4162351d88f7648d741cdd0c"
- hash_key = "Hash_76cf2d18fa656820d79d13cad11bf3e613fdb0" \
- "6ff80f968ba1755d27cdf5eab3"
+ cla_key_a = "TestAsDhall::SomeTestClass_5af4256bc953a" \
+ "30de368b9707b0d79bb119acc3098ba9589f234125eed296a19"
+ cla_key_b = "TestAsDhall::SomeTestClass_eca4d00787f4b" \
+ "c9f52ef84ce193c62e28a31a9de5704319d738b1b9ca4a6abd7"
union_type = Dhall::UnionType.new(
alternatives: {
"Natural" => Dhall::Builtins[:Natural],
"Text" => Dhall::Builtins[:Text],
"None" => nil,
"boop" => nil,
- "Object" => Dhall::EmptyRecordType.new,
"Bool" => Dhall::Builtins[:Bool],
- hash_key => Dhall::RecordType.new(
+ "Hash" => Dhall::RecordType.new(
record: {
"a" => Dhall::Builtins[:Natural]
}
),
- array_key => Dhall::Application.new(
+ "Array" => Dhall::Application.new(
function: Dhall::Builtins[:List],
argument: Dhall::Builtins[:Natural]
+ ),
+ cla_key_a => Dhall::RecordType.new(
+ record: {
+ "a" => Dhall::Builtins[:Natural],
+ "b" => Dhall::Builtins[:Text]
+ }
+ ),
+ cla_key_b => Dhall::RecordType.new(
+ record: {
+ "a" => Dhall::Builtins[:Text],
+ "b" => Dhall::Builtins[:Natural]
+ }
)
}
)
+ expr = [
+ 1, "hai", nil, :boop, true, false, { a: 1 }, [1],
+ SomeTestClass.new, SomeTestClass.new(a: "hai", b: 1)
+ ].as_dhall
+
+ assert_equal(
+ Dhall::Builtins[:List].call(union_type).normalize,
+ Dhall::TypeChecker.type_of(expr).normalize
+ )
+
assert_equal(
Dhall::List.new(elements: [
Dhall::Union.from(union_type, "Natural", Dhall::Natural.new(value: 1)),
Dhall::Union.from(union_type, "Text", Dhall::Text.new(value: "hai")),
Dhall::Union.from(union_type, "None", nil),
Dhall::Union.from(union_type, "boop", nil),
- Dhall::Union.from(union_type, "Object", Dhall::EmptyRecord.new),
Dhall::Union.from(union_type, "Bool", Dhall::Bool.new(value: true)),
Dhall::Union.from(union_type, "Bool", Dhall::Bool.new(value: false)),
- Dhall::Union.from(union_type, hash_key, Dhall::Record.new(
+ Dhall::Union.from(union_type, "Hash", Dhall::Record.new(
record: { "a" => Dhall::Natural.new(value: 1) }
)),
- Dhall::Union.from(union_type, array_key, Dhall::List.new(
+ Dhall::Union.from(union_type, "Array", Dhall::List.new(
elements: [Dhall::Natural.new(value: 1)]
+ )),
+ Dhall::Union.from(union_type, cla_key_a, Dhall::Record.new(
+ record: {
+ "a" => Dhall::Natural.new(value: 1),
+ "b" => Dhall::Text.new(value: "hai")
+ }
+ )),
+ Dhall::Union.from(union_type, cla_key_b, Dhall::Record.new(
+ record: {
+ "a" => Dhall::Text.new(value: "hai"),
+ "b" => Dhall::Natural.new(value: 1)
+ }
))
]),
- [1, "hai", nil, :boop, Object.new, true, false, { a: 1 }, [1]].as_dhall
+ expr
)
end
@@ 213,13 252,6 @@ class TestAsDhall < Minitest::Test
)
end
- class SomeTestClass
- def initialize
- @a = 1
- @b = "hai"
- end
- end
-
def test_object
assert_equal(
Dhall::Union.new(