The project should reconsider the "inference-based" dispatch used by the static compilation feature of Groovy 2.0.
The problem with "inference-based" dispatch is that it is difficult to understand and predict what methods the compiler will choose to invoke. Groovy's dynamic dispatch is understandable: the method to be invoked is determined by the types of the parameters at runtime. Java static dispatch is understandable and predictable: the method is determined by the types of the parameters at compile time. Groovy's "inference-based" dispatch is neither.
Furthermore, the "inference-based" dispatch strategy actually ignores type information provided by the programmer in favor of its own inferred types, which is indefensible. Obviously, since Groovy does not require the developer to specify types, type inference is necessary. But when the developer does specify types, the compiler should absolutely honor those types.
Consider the attached test code.
Example 1 makes perfect sense; no type is provided, so the compiler infers String and calls f(String).
Example 2 also makes sense; the type is String.
Example 3 is surprising. Here I've specifically given the type as Object because I want to call f(Object). But Groovy calls f(String) because it "knows" that s2 is really a String. Who's in charge here? I, the developer, have chosen to add a @CompileStatic annotation to this class, so I have chosen a compilation mode that depends on static types. And the static type that I've deliberately given is Object. If I wanted f(String) I could have specified s2 as String or just "def." But I wrote Object and want the compiler to honor that.
Example 4 is the "workaround" to the Example 3 problem: I can cast the s2 value to Object, its own type! Why does the compiler honor the type cast but not the declared type?
Examples 5, 6, and 7 show that the type inference extends across methods, but only sometimes. If the method can provably only return String, the compiler invokes f(String). But if the compiler can't prove that the method always returns String, it will invoke f(Object), even if the method does always return String in practice. The really strange part is that the same source code calls different methods depending on whether or not it's part of the same compilation unit. Simply moving the reallyReturnsString method to a different class leads to a different invocation of f.
If the static compiler used the developer-provided types, and only inferred types when they were not specified in the code, then it would be regular static dispatch with type inference, as used successfully by other static compilers such as Scala. It wouldn't be as clever, but it wouldn't be less "Groovy" than the inference-based strategy. Static dispatch is inherently not "Groovy." Trying to make it more "Groovy" by ignoring types sometimes, but not other times, depending on the compiler's ability to work out its own types, and introducing oddities like casting a value to its own declared type, makes it surprising and unusual, which is definitely not "Groovy."