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

Hash compare by identity #8451

Merged
merged 5 commits into from
Nov 8, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions spec/std/hash_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1187,4 +1187,32 @@ describe "Hash" do
end
end
end

describe "compare_by_identity" do
it "small hash" do
string = "foo"
h = {string => 1}
h.compare_by_identity?.should be_false
h.compare_by_identity
h.compare_by_identity?.should be_true
h[string]?.should eq(1)
h["fo" + "o"]?.should be_nil
end

it "big hash" do
h = {} of String => Int32
nums = (100..116).to_a
strings = nums.map(&.to_s)
strings.zip(nums) do |string, num|
h[string] = num
end
h.compare_by_identity
nums.each do |num|
h[num.to_s]?.should be_nil
end
strings.zip(nums) do |string, num|
h[string]?.should eq(num)
end
end
end
end
13 changes: 13 additions & 0 deletions spec/std/set_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -396,4 +396,17 @@ describe "Set" do
end

typeof(Set(Int32).new(initial_capacity: 1234))

it "compares by identity" do
string = "foo"
set = Set{string, "bar", "baz"}
set.compare_by_identity?.should be_false
set.includes?(string).should be_true

set.compare_by_identity
set.compare_by_identity?.should be_true

set.includes?("fo" + "o").should be_false
set.includes?(string).should be_true
end
end
6 changes: 3 additions & 3 deletions src/compiler/crystal/codegen/exception.cr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require "./codegen"

class Crystal::CodeGenVisitor
@node_ensure_exception_handlers = {} of UInt64 => Handler
@node_ensure_exception_handlers : Hash(ASTNode, Handler) = ({} of ASTNode => Handler).compare_by_identity

def visit(node : ExceptionHandler)
# In this codegen, we assume that LLVM only provides us with a basic try/catch abstraction with no
Expand Down Expand Up @@ -289,7 +289,7 @@ class Crystal::CodeGenVisitor
end

def execute_ensures_until(node)
stop_exception_handler = @node_ensure_exception_handlers[node.object_id]?.try &.node
stop_exception_handler = @node_ensure_exception_handlers[node]?.try &.node

@ensure_exception_handlers.try &.reverse_each do |exception_handler|
break if exception_handler.node.same?(stop_exception_handler)
Expand All @@ -305,7 +305,7 @@ class Crystal::CodeGenVisitor

def set_ensure_exception_handler(node)
if eh = @ensure_exception_handlers.try &.last?
@node_ensure_exception_handlers[node.object_id] = eh
@node_ensure_exception_handlers[node] = eh
end
end

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module Crystal
# associated to a def's object id (the UInt64), and on an instantiation
# we compare the new type with the previous one and check if it contains
# the previous type.
getter splat_expansions = {} of UInt64 => Type
getter splat_expansions : Hash(Def, Type) = ({} of Def => Type).compare_by_identity

# All FileModules indexed by their filename.
# These store file-private defs, and top-level variables in files other
Expand Down
8 changes: 4 additions & 4 deletions src/compiler/crystal/semantic/bindings.cr
Original file line number Diff line number Diff line change
Expand Up @@ -217,16 +217,16 @@ module Crystal
owner_trace = [] of ASTNode
node = self

visited = Set(typeof(object_id)).new
visited = Set(ASTNode).new.compare_by_identity
owner_trace << node if node.type?.try &.includes_type?(owner)
visited.add node.object_id
visited.add node
while deps = node.dependencies?
dependencies = deps.select { |dep| dep.type? && dep.type.includes_type?(owner) && !visited.includes?(dep.object_id) }
dependencies = deps.select { |dep| dep.type? && dep.type.includes_type?(owner) && !visited.includes?(dep) }
if dependencies.size > 0
node = dependencies.first
nil_reason = node.nil_reason if node.is_a?(MetaTypeVar)
owner_trace << node if node
visited.add node.object_id
visited.add node
else
break
end
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/crystal/semantic/call_error.cr
Original file line number Diff line number Diff line change
Expand Up @@ -638,14 +638,14 @@ class Crystal::Call
def check_recursive_splat_call(a_def, args)
if a_def.splat_index
current_splat_type = args.values.last.type
if previous_splat_type = program.splat_expansions[a_def.object_id]?
if previous_splat_type = program.splat_expansions[a_def]?
if current_splat_type.has_in_type_vars?(previous_splat_type)
raise "recursive splat expansion: #{previous_splat_type}, #{current_splat_type}, ..."
end
end
program.splat_expansions[a_def.object_id] = current_splat_type
program.splat_expansions[a_def] = current_splat_type
yield
program.splat_expansions.delete a_def.object_id
program.splat_expansions.delete a_def
else
yield
end
Expand Down
8 changes: 4 additions & 4 deletions src/compiler/crystal/semantic/cleanup_transformer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ module Crystal
# idea on how to generate code for unreachable branches, because they have no type,
# and for now the codegen only deals with typed nodes.
class CleanupTransformer < Transformer
@transformed : Set(Def)

def initialize(@program : Program)
@transformed = Set(UInt64).new
@transformed = Set(Def).new.compare_by_identity
@def_nest_count = 0
@last_is_truthy = false
@last_is_falsey = false
Expand Down Expand Up @@ -361,9 +363,7 @@ module Crystal
end

target_defs.each do |target_def|
unless @transformed.includes?(target_def.object_id)
@transformed.add(target_def.object_id)

if @transformed.add?(target_def)
node.bubbling_exception do
@def_nest_count += 1
target_def.body = target_def.body.transform(self)
Expand Down
7 changes: 3 additions & 4 deletions src/compiler/crystal/semantic/fix_missing_types.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ require "../semantic"

class Crystal::FixMissingTypes < Crystal::Visitor
@program : Program
@fixed : Set(UInt64)
@fixed : Set(Def)

def initialize(mod)
@program = mod
@fixed = Set(typeof(object_id)).new
@fixed = Set(Def).new.compare_by_identity
end

def visit(node : Def)
Expand Down Expand Up @@ -71,8 +71,7 @@ class Crystal::FixMissingTypes < Crystal::Visitor
end

node.target_defs.try &.each do |target_def|
unless @fixed.includes?(target_def.object_id)
@fixed.add(target_def.object_id)
if @fixed.add?(target_def)
target_def.type = @program.no_return unless target_def.type?
target_def.accept_children self
end
Expand Down
8 changes: 4 additions & 4 deletions src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1633,7 +1633,7 @@ module Crystal
@scope : Type
@in_super : Int32
@callstack : Array(ASTNode)
@visited : Set(UInt64)?
@visited : Set(Def)?
@vars : MetaVars

def initialize(a_def, @scope, @vars)
Expand Down Expand Up @@ -1687,10 +1687,10 @@ module Crystal

node.target_defs.try &.each do |target_def|
if target_def.owner == @scope
next if visited.try &.includes?(target_def.object_id)
next if visited.try &.includes?(target_def)

visited = @visited ||= Set(typeof(object_id)).new
visited << target_def.object_id
visited = @visited ||= Set(Def).new.compare_by_identity
visited << target_def

@callstack.push(node)
target_def.body.accept self
Expand Down
8 changes: 4 additions & 4 deletions src/compiler/crystal/tools/context.cr
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ module Crystal
end

class RechableVisitor < Visitor
@visited_typed_defs : Set(Def)

def initialize(@context_visitor : Crystal::ContextVisitor)
@visited_typed_defs = Set(UInt64).new
@visited_typed_defs = Set(Def).new.compare_by_identity
end

def visit(node : Call)
Expand All @@ -91,9 +93,7 @@ module Crystal
end

def visit(node : Def)
should_visit = !@visited_typed_defs.includes?(node.object_id)
@visited_typed_defs << node.object_id if should_visit
return should_visit
@visited_typed_defs.add?(node)
end

def visit(node)
Expand Down
14 changes: 7 additions & 7 deletions src/compiler/crystal/tools/formatter.cr
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ module Crystal
@assign_infos : Array(AlignInfo)
@doc_comments : Array(CommentInfo)
@current_doc_comment : CommentInfo?
@hash_in_same_line : Set(UInt64)
@hash_in_same_line : Set(ASTNode)
@shebang : Bool
@heredoc_fixes : Array(HeredocFix)
@assign_length : Int32?
Expand Down Expand Up @@ -136,7 +136,7 @@ module Crystal
@assign_infos = [] of AlignInfo
@doc_comments = [] of CommentInfo
@current_doc_comment = nil
@hash_in_same_line = Set(UInt64).new
@hash_in_same_line = Set(ASTNode).new.compare_by_identity
@shebang = @token.type == :COMMENT && @token.value.to_s.starts_with?("#!")
@heredoc_fixes = [] of HeredocFix
@last_is_heredoc = false
Expand Down Expand Up @@ -896,7 +896,7 @@ module Crystal
format_hash_entry nil, node_of
end

if @hash_in_same_line.includes? node.object_id
if @hash_in_same_line.includes? node
@hash_infos.reject! { |info| info.id == node.object_id }
end

Expand Down Expand Up @@ -930,8 +930,8 @@ module Crystal
skip_space_or_newline
accept entry.value

if found_in_same_line
@hash_in_same_line << hash.object_id
if hash && found_in_same_line
@hash_in_same_line << hash
end
end

Expand All @@ -941,7 +941,7 @@ module Crystal
format_literal_elements node.entries, :"{", :"}"
@current_hash = old_hash

if @hash_in_same_line.includes? node.object_id
if @hash_in_same_line.includes? node
@hash_infos.reject! { |info| info.id == node.object_id }
end

Expand All @@ -965,7 +965,7 @@ module Crystal
accept entry.value

if found_in_same_line
@hash_in_same_line << hash.object_id
@hash_in_same_line << hash
end
end

Expand Down
Loading