Crystal: Module validation failed: Function return type does not match operand type of return inst

Created on 27 Oct 2018  路  17Comments  路  Source: crystal-lang/crystal

Found this compiler bug when duplicating and modifying test data hashes in my specs.

Code to reproduce

x = {
  "y" => [] of Hash(String, String | Hash(String, String))
}
x["y"].dup.concat [{ "a" => "b" }]

Compiler output

Module validation failed: Function return type does not match operand type of return inst!
  ret i32* %1, !dbg !36
 %"Hash(String, Hash(String, String) | String)"* (Exception)
  from Crystal::CodeGenVisitor#finish:Nil
  from Crystal::Compiler#codegen<Crystal::Program, Crystal::ASTNode+, Array(Crystal::Compiler::Source), String>:(Tuple(Array(Crystal::Compiler::CompilationUnit), Array(String)) | Nil)
  from Crystal::Compiler#compile<Array(Crystal::Compiler::Source), String>:Crystal::Compiler::Result
  from Crystal::Command#run_command<Bool>:Nil
  from Crystal::Command#run:(Bool | Crystal::Compiler::Result | Nil)
  from main
Error: you've found a bug in the Crystal compiler. Please open an issue, including source code that will allow us to reproduce the bug: https://github.com/crystal-lang/crystal/issues

Environment

Crystal 0.26.1 (2018-09-26)

LLVM: 6.0.1
Default target: x86_64-apple-macosx

References

Might be related to #6253

bug topicsemantic

All 17 comments

same here :

Module validation failed: Function return type does not match operand type of return inst!          
  ret i32* %1, !dbg !24                                                                             
 %"Hash(String, Int32 | String)"* (Exception)                                                       
  from ???                                                                                          
  from ???                                                                                          
  from ???                                                                                          
  from ???                                                                                          
  from ???                                                                                          
  from ???                                                                                          
  from ???                                                                                          
  from ???                                                                                          
  from ???                                                                                          

This is the real error, visible when the compiler is downgraded to 0.23.1: https://carc.in/#/r/5mhm

I'll bisect.

I traced the problem down to https://github.com/crystal-lang/crystal/pull/5025

It's fairly easy to revert, which does fix the problem and I posted a revert here: https://github.com/RX14/crystal/commit/afad608ebe5f41e535c3898dc4a015a6bca233b0. If someone wants to figure out the root cause they can.

@RX14 thanks for bisecting.

It seems that calling #dup/#clone somehow breaks things, only for empty arrays though... any idea why's that? /cc @asterite

The code shouldn't compile at all, the types are not compatible.

The code is trying to insert an Hash(String, String) into something that holds Hash(String, String | Hash(String, String)).

This is the code that shouldn't compile:

class Gen(T)
end

p = Pointer(Gen(String | Int32)).malloc(1)
p.value = Gen(String).new

Because it shouldn't compile, but it does, the end result probably is something that the codegen doesn't expect. I'm not sure.

This has to do with covariance/contravariance/invariance and I won't have time to look at it.

This is somehow completely broken... I don't know what happened:

a = [] of Array(String | Int32)
puts typeof(a) # => Array(Array(Int32 | String))
a << [1] # ???
z = a[0]
puts typeof(a) # => Array(Array(Int32 | String))
puts typeof(z) # => (Array(Int32 | String) | Array(Int32)) # Huh???

I can make it give a compile error by hardcoding something in the Pointer logic in the compiler, though the error will be ugly (but it's better than a compile error)

As a first step, I think we need a way to tell whether a type parameter in restriction should be an exact match or an upper bound match. For example:

def strict(x : Array(Int32 | String))
end

def nonstrict(x : Array(<(Int32 | String))
end

strict(Array(Int32).new) # Error
strict(Array(String).new) # Error
strict(Array(Int32 | String).new) # OK

nonstrict(Array(Int32).new) # OK
nonstrict(Array(String).new) # OK
nonstrict(Array(Int32 | String).new) # OK

Then a restriction like def push(value : T) inside Array would mean it's a strict match, and if it's an Array(Array(Int32 | String)) then you can't pass an Array(Int32).

We have stuff like x : Range(Int, Int) that means "a range of any integer type", but that would now stop to work and you'll have to do Range(<Int, <Int).

I say let's make type parameter restrictions be a strict match, and later (much later) we can implement the < operator in type restrictions, if we really need to (you can always omit them and rely on duck typing).

@asterite I'd prefer def nonstrict(x : Array(T)) forall T < Int32 | String, which makes it much clearer that it's generating multiple semantically different methods.

This is a massive change to the language though. I don't know how we missed this :(

Shouldn't this discussion better continue in #3803?

I've been implementing event driven architecture and got that bug recently (code reduced) (https://carc.in/#/r/5whz):

abstract struct Event
end

class MyChannel
  def subscribe(event : T.class, &proc : Proc(T, Nil)) forall T
    ary = Array(ProcSubscription(Event)).new
    sub = ProcSubscription(T).new(proc)

    unless ary.includes?(sub)
      ary << sub
    end
  end

  struct ProcSubscription(T)
    def initialize(@proc : Proc(T, Nil))
    end

    def call(event)
      @proc.call(event.as(T))
    end
  end
end

struct FooEvent < Event
  getter foo

  def initialize(@foo : String)
  end
end

channel = MyChannel.new

channel.subscribe(FooEvent) do |event|
  puts "Got FooEvent: #{event.foo}"
end
Module validation failed: Function return type does not match operand type of return inst!
  ret %"(MyChannel::ProcSubscription(Event+) | MyChannel::ProcSubscription(FooEvent))" %1, !dbg !11
 %"MyChannel::ProcSubscription(Event+)" = type { %"->" } (Exception)

I've narrowed the bug to these lines:

    unless ary.includes?(sub)
      ary << sub
    end

Once unless wrapper is removed, it compiles nicely (https://carc.in/#/r/5wi0):

-    unless ary.includes?(sub)
      ary << sub
-    end

Basically, any trial to return bool fails with the same bug:

if ary.includes?(sub)
  return false
end
ary << sub
return true

Also found this bug, probably related:

lib LibFoo
  struct S
    callback : ->
  end
end

s = LibFoo::S.new
s.callback = nil
Module validation failed: Call parameter type does not match function signature!
%Nil ()* null
 %"->" = type { i8*, i8* }  %71 = call i8* @"~check_proc_is_not_closure"(%Nil ()* null), !dbg !74
 (Exception)
  from Crystal::CodeGenVisitor#finish:Nil
  from Crystal::Compiler#codegen<Crystal::Program, Crystal::ASTNode+, Array(Crystal::Compiler::Source), String>:(Tuple(Array(Crystal::Compiler::CompilationUnit), Array(String)) | Nil)
  from Crystal::Compiler#compile<Array(Crystal::Compiler::Source), String>:Crystal::Compiler::Result
  from Crystal::Command#run_command<Bool>:Nil
  from Crystal::Command#run:(Bool | Crystal::Compiler::Result | Nil)
  from __crystal_main
  from main

@decademoon luckily that's not related! please make another issue :)

@RX14 #7279

I'm also hitting this compiler error, but it does not reproduce reliably.

Module validation failed: Function return type does not match operand type of return inst!
  ret %"(Array(AnyHash::JSONTypes::Value)+ | Bool | Float32 | Float64 | Hash(String | Symbol, AnyHash::JSONTypes::Value)+ | Int128 | Int16 | Int32 | Int64 | Int8 | JSON::Any | Set(AnyHash::JSONTypes::Value) | String | Symbol | Time | UInt128 | UInt16 | UInt32 | UInt64 | UInt8 | Nil)" %1, !dbg !21
 %"(Array(AnyHash::JSONTypes::Value) | Bool | Float32 | Float64 | Hash(String | Symbol, AnyHash::JSONTypes::Value) | Int128 | Int16 | Int32 | Int64 | Int8 | JSON::Any | Set(AnyHash::JSONTypes::Value) | String | Symbol | Time | UInt128 | UInt16 | UInt32 | UInt64 | UInt8 | Nil)" = type { i32, [3 x i64] } (Exception)

It happened on CI: https://circleci.com/gh/shardbox/shardbox-core/22
In the next build https://circleci.com/gh/shardbox/shardbox-core/24 it did not occur, but there was no change to the Crystal code in between.

@straight-shoota there's also #6253

Thanks @Sija I didn't notice this other issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

oprypin picture oprypin  路  3Comments

asterite picture asterite  路  3Comments

nabeelomer picture nabeelomer  路  3Comments

relonger picture relonger  路  3Comments

lbguilherme picture lbguilherme  路  3Comments