Repository: goby-lang/goby
Pull request #775 (2019-07-28 06:59:40)

Implements kind_of? to builtinClassCommonInstanceMethods
When I was writing test code for goby.
I noticed that ruby has kind_of? method. goby doesn't have it.
I wanna use kind_of? method. so I implement kind_of? method.

```
$ irb
irb(main):003:0> [].kind_of?(Object)
=> true
```

```
$ goby -i
» [].kind_of?(Object)
» NoMethodError: Undefined Method 'kind_of?' for []
```

In case of Ruby. is_a? method is alias to kind_of?
https://github.com/ruby/ruby/blob/b40589802b6fe6ba5cbb783a43f513cb2e597844/object.c#L4318

But I don't know to create alias in goby implementation.
So I implemented kind_of? method as a separate implementation.


Patch:
diff --git a/cmd/binder/main.go b/cmd/binder/main.go
index be9d964..6dfd14c 100644
--- a/cmd/binder/main.go
+++ b/cmd/binder/main.go
@@ -80,8 +80,8 @@ func allArgs(f *ast.FieldList) []argPair {
 // Binding holds context about a struct that represents a goby class.
 type Binding struct {
 	ClassName       string
-	ClassMethods    []*ast.FuncDecl // Any method defined without a pointer receiver is a class method func (Class) myFunc
-	InstanceMethods []*ast.FuncDecl // Any method defined with a pointer receiver is an instance method func (c *Class) myFunc
+	ClassMethods    []*ast.FuncDecl // Any method defined without a pointer reciever is a class method func (Class) myFunc
+	InstanceMethods []*ast.FuncDecl // Any method defined with a pointer reciever is an instance method func (c *Class) myFunc
 
 }
 
diff --git a/vm/class.go b/vm/class.go
index 901d298..7a683b7 100644
--- a/vm/class.go
+++ b/vm/class.go
@@ -953,6 +953,51 @@ var builtinClassCommonInstanceMethods = []*BuiltinMethodObject{
 			return FALSE
 		},
 	},
+	{
+		// Returns true if Object class is equal to the input argument class
+		//
+		// ```ruby
+		// "Hello".kind_of?(String)            # => true
+		// 123.kind_of?(Integer)               # => true
+		// [1, true, "String"].kind_of?(Array) # => true
+		// { a: 1, b: 2 }.kind_of?(Hash)       # => true
+		// "Hello".kind_of?(Integer)           # => false
+		// 123.kind_of?(Range)                 # => false
+		// (2..4).kind_of?(Hash)               # => false
+		// nil.kind_of?(Integer)               # => false
+		// ```
+		//
+		// @param n/a []
+		// @return [Boolean]
+		Name: "kind_of?",
+		Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
+			if len(args) != 1 {
+				return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgument, 1, len(args))
+			}
+
+			c := args[0]
+			gobyClass, ok := c.(*RClass)
+
+			if !ok {
+				return t.vm.InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.ClassClass, c.Class().Name)
+			}
+
+			receiverClass := receiver.Class()
+
+			for {
+				if receiverClass.Name == gobyClass.Name {
+					return TRUE
+				}
+
+				if receiverClass.Name == classes.ObjectClass {
+					break
+				}
+
+				receiverClass = receiverClass.superClass
+			}
+			return FALSE
+		},
+	},
 	// Checks if the class of the instance has been activated with `inherits_method_missing`.
 	//
 	// ```ruby


Test patch:
diff --git a/vm/class_test.go b/vm/class_test.go
index d56db7e..5e32e56 100644
--- a/vm/class_test.go
+++ b/vm/class_test.go
@@ -1355,6 +1355,73 @@ func TestGeneralIsAMethodFail(t *testing.T) {
 	}
 }
 
+func TestGeneralKindOfMethod(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected bool
+	}{
+		{`123.kind_of?(Integer)`, true},
+		{`123.kind_of?(Object)`, true},
+		{`123.kind_of?(String)`, false},
+		{`123.kind_of?(Range)`, false},
+		{`"Hello World".kind_of?(String)`, true},
+		{`"Hello World".kind_of?(Object)`, true},
+		{`"Hello World".kind_of?(Boolean)`, false},
+		{`"Hello World".kind_of?(Array)`, false},
+		{`(2..10).kind_of?(Range)`, true},
+		{`(2..10).kind_of?(Object)`, true},
+		{`(2..10).kind_of?(Null)`, false},
+		{`(2..10).kind_of?(Hash)`, false},
+		{`{ a: 1, b: "2", c: ["Goby", 123] }.kind_of?(Hash)`, true},
+		{`{ a: 1, b: "2", c: ["Goby", 123] }.kind_of?(Object)`, true},
+		{`{ a: 1, b: "2", c: ["Goby", 123] }.kind_of?(Class)`, false},
+		{`{ a: 1, b: "2", c: ["Goby", 123] }.kind_of?(Array)`, false},
+		{`[1, 2, 3, 4, 5].kind_of?(Array)`, true},
+		{`[1, 2, 3, 4, 5].kind_of?(Object)`, true},
+		{`[1, 2, 3, 4, 5].kind_of?(Null)`, false},
+		{`[1, 2, 3, 4, 5].kind_of?(String)`, false},
+		{`true.kind_of?(Boolean)`, true},
+		{`true.kind_of?(Object)`, true},
+		{`true.kind_of?(Array)`, false},
+		{`true.kind_of?(Integer)`, false},
+		{`String.kind_of?(Class)`, true},
+		{`String.kind_of?(String)`, false},
+		{`String.kind_of?(Array)`, false},
+		{`nil.kind_of?(Null)`, true},
+		{`nil.kind_of?(Object)`, true},
+		{`nil.kind_of?(String)`, false},
+		{`nil.kind_of?(Range)`, false},
+	}
+
+	for i, tt := range tests {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		VerifyExpected(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, 0)
+		v.checkSP(t, i, 1)
+	}
+}
+
+func TestGeneralKindOfMethodFail(t *testing.T) {
+	testsFail := []errorTestCase{
+		{`123.kind_of?`, "ArgumentError: Expect 1 argument(s). got: 0", 1},
+		{`Class.kind_of?`, "ArgumentError: Expect 1 argument(s). got: 0", 1},
+		{`123.kind_of?(123, 456)`, "ArgumentError: Expect 1 argument(s). got: 2", 1},
+		{`123.kind_of?(Integer, String)`, "ArgumentError: Expect 1 argument(s). got: 2", 1},
+		{`123.kind_of?(true)`, "TypeError: Expect argument to be Class. got: Boolean", 1},
+		{`Class.kind_of?(true)`, "TypeError: Expect argument to be Class. got: Boolean", 1},
+	}
+
+	for i, tt := range testsFail {
+		v := initTestVM()
+		evaluated := v.testEval(t, tt.input, getFilename())
+		checkErrorMsg(t, i, evaluated, tt.expected)
+		v.checkCFP(t, i, tt.expectedCFP)
+		v.checkSP(t, i, 1)
+	}
+}
+
+
 func TestGeneralIsNilMethod(t *testing.T) {
 	tests := []struct {
 		input    string