Xojo WTF

I could make it fail in 64bit with the right values.

Basically. There was a big thread about it and Xojo decided they won’t fix it.

1 Like

In another thread we stumbled upon a behavior which is confusing many people:

var m1,m2 as MemoryBlock
m1 = "foo"
m2 = "foo"

m1 =  m2 // true (? this is surprising)
m1 is m2 // false

The surprising part is that usually in Xojo, when you compare two objects with the = operator, it tells you if the objects are the same object (e.g. one object with two references to it). In this case, MemoryBlocks implement the Operator_Compare operator, so the equals operator (and > and < ) are actually comparing the Contents of the memoryBlocks.

FWIW, you did not prove this in the other thread. Did you actually run the code you posted here?

1 Like

Yes! tested in Xojo 2025R3, 2019R1.1, and RealStudio 2011. All give same results.

To make sure it’s not some weird aliasing of string constants, I did this test:

It probably has compare operator on it given this result.

Sadly then I do not think Xojo has anyway to compare the reference if there is Compare operator on it ?? ……..perhaps if you cast to object it avoids the compare operator overload and does the reference compare….am not sure, like if Object(m1) = Object(m2) then

1 Like

The Is operator compares object identity. Is — Xojo documentation

This is why I prefer to use If (Obj Is Nil) = False Then instead of If Obj <> Nil Then. Usually, I’m not going to compare the contents of an object against nil using Operator_Compare.

5 Likes

@Thom_McGrath I used to worry about this myself, but I think that comparisons against Nil are special, and the compiler will never invoke Operator_Convert in that case. I can’t find a way to falsify this claim.

class Class1
 Public Function Operator_Convert() As Object
  ' always return nil
  return nil
End Function


var c1 as new Class1
if c1 = nil then
  break // this never happens
else if c1 is nil then
  break // this never happens
end if

You can get it to fail if you use an intermediary variable that is a different Class

class Class1
 Public Function Operator_Convert() As Ptr
  ' always return nil
  return nil
End Function


var c1 as new Class1
var p as Ptr = c1 // this will be nil

But I think in the one-liner case, comparing any object = nil is safe - Operator_Convert() will never be invoked.

Do you have a counter-example?

I’m still waiting for an explanation of why this isn’t a compiler bug. To venture a guess, I bet Operator_Compare is turning both MemoryBlocks into strings and doing that comparison.

Well you’re using Operator_Convert instead of Operator_Compare. Compare is definitely invoked against nil.

The code that triggered this was simply:

Var Instance As New TestClass
If Instance <> Nil Then
  Break
Else
  Break
End If

Inside Operator_Compare, you must use Is or else you trigger a stack overflow.

4 Likes

If I recall correctly - though I’m not very confident of this - Operator_Convert is only called for assignments, not for comparisons. So that would mean MemoryBlock would have to have an Operator_Compare(Other As MemoryBlock) As Integer function.

You can try it yourself. Create a class with a Operator_Convert() As String function, then try If Instance = "Some String" Then and you’ll get a compile error because no comparison operator has been defined, even though there is a way to compare the two with a conversion.

1 Like

Interesting point - what should Operator_Compare() return against a nil object?

If you return -1 or +1, then the equals (=) and not equals (<>) operators work fine and return False as expected. But then the less-than (<) or greater-than (>) comparisons would return a bogus True value.

Public Function Operator_Compare(other as Class1) As Integer
  // by definition, self is not nil, so if other is nil, we aren't equal
  if other is Nil then
    return -1
  end if
    
End Function

var c1 as new Class1
var c2 as Class1 = nil

if c1 = c2 then // False (as expected)
if c1 > c2 then // False (makes sense, I suppose)
if c1 < c2 then // True  (this is...unexpected)

Perhaps the right answer is to throw an exception:

Public Function Operator_Compare(other as Class1) As Integer
  if other is nil then
     raise new RuntimeException("Can't compare instance of Class1 to nil")
  end if    
End Function

In fact, here is how Xojo handles comparing a non-nil MemoryBlock against a Nil one:

var m1,m2 as MemoryBlock

m1 = "foo"
m2 = nil

  m1 =  m2 : False
  m1 <  m2 : False
  m1 >  m2 : True
  m1 is m2 : False

I can sort of see the logic here - it follows what you would get with string comparisons, e.g a non-empty string is always greater than and never less than, an empty string.

In my opinion, this is so exceedingly ambiguous that it should be a compiler error. Object references do not have quantitative differences; they do not exist on a scale or within a range.

Of course, this is probably the result of (again, in my opinion) inappropriate Object_Compare shenanigans coercing those poor MemoryBlocks into strings.

No, Operator_Compare will not coerce the memory blocks into strings. And object comparison has its uses. DateTime is a perfect example. What you’re seeing is how Xojo decided to compare two memory blocks. In any sort of object comparison, you need to consider nil. I see nothing here that doesn’t make sense.

2 Likes

Yes, it’s a perfect example because the value of the object exists on a well-defined scale. Not so with MemoryBlocks. They’re arbitrary collections of bytes. Why compare them as strings? Why not compare them as extremely large floating point values? Or by how many of the bytes are 0x4F?

Maybe they aren’t comparing them as strings but byte by byte?

1 Like

Let’s check the documentation!

Oh wait, it’s boilerplate garble that reveals no insights.

Operator_Compare(value As MemoryBlock) As Integer

Defines the following comparison operators for the MemoryBlock class: =, <, >, <=, >=, <>. It compares the passed MemoryBlock to the Self instance.

Operator_Compare returns an integer whose meaning is as follows: < 0 means that Self is less than the passed parameter, 0 means that Self is equal to the passed parameter, and > 0 means that Self is greater than the passed parameter.

1 Like

Actually this statement is wrong, since Other is nil, there isn’t an instance to call a recursive Operator_Compare on. So <> ends up being perfectly safe inside Operator_Compare since it falls back to an identity comparison.

1 Like

Some more Array oddities:

Although an Array behaves like an Object (an instance of a Class), it’s really not:

  • there is no class named “Array” … so this does not work:
var x as Array  // error
  • you can not New an Array like you would with other Object classes - there is no Array Constructor
var x() as integer = New Array(1,2,3,4,5) // error
var x() as integer = Array(1,2,3,4,5) // OK
  • there are some weird rules about conversions
var x() as Double
x.append 1
x.append 2
x.append 3  // this works: you can append an Integer to an array of Double

var z() as Double = Array(1,2,3) 
// Error: Type mismatch error.  Expected Double(), but got Int64()

var z() as Double = Array(1, 2.0 ,3) 
// this works - as long as one of the items is a Double

2 Likes