blob: 37e0a99d51d4139ae7a504103f8622436fefb493 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy.transform.stc
/**
* Unit tests for static type checking : bug fixes.
*/
class BugsSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-5456
void testShouldNotAllowDivOnUntypedVariable() {
shouldFailWithMessages '''
def foo(Closure cls) {}
def bar() { foo { it / 2 } }
''', 'Cannot find matching method java.lang.Object#div(int)'
}
void testShouldNotAllowDivBynUntypedVariable() {
shouldFailWithMessages '''
def foo(Closure cls) {}
def bar() { foo { 2 / it } }
''', 'Cannot find matching method int#div(java.lang.Object)'
}
void testShouldNotAllowModOnUntypedVariable() {
shouldFailWithMessages '''
def foo(Closure cls) {}
def bar() { foo { it % 2 } }
''', 'Cannot find matching method java.lang.Object#mod(int)'
}
void testShouldNotAllowModBynUntypedVariable() {
shouldFailWithMessages '''
def foo(Closure cls) {}
def bar() { foo { 2 % it } }
''', 'Cannot find matching method int#mod(java.lang.Object)'
}
void testShouldNotAllowMulOnUntypedVariable() {
shouldFailWithMessages '''
def foo(Closure cls) {}
def bar() { foo { it * 2 } }
''', 'Cannot find matching method java.lang.Object#multiply(int)'
}
void testShouldNotAllowMulBynUntypedVariable() {
shouldFailWithMessages '''
def foo(Closure cls) {}
def bar() { foo { 2 * it } }
''', 'Cannot find matching method int#multiply(java.lang.Object)'
}
void testShouldNotAllowPlusOnUntypedVariable() {
shouldFailWithMessages '''
def foo(Closure cls) {}
def bar() { foo { it + 2 } }
''', 'Cannot find matching method java.lang.Object#plus(int)'
}
void testShouldNotAllowPlusWithUntypedVariable() {
shouldFailWithMessages '''
def foo(Closure cls) {}
def bar() { foo { 2 + it } }
''', 'Cannot find matching method int#plus(java.lang.Object)'
}
void testShouldNotAllowMinusOnUntypedVariable() {
shouldFailWithMessages '''
def foo(Closure cls) {}
def bar() { foo { it - 2 } }
''', 'Cannot find matching method java.lang.Object#minus(int)'
}
void testShouldNotAllowMinusByUntypedVariable() {
shouldFailWithMessages '''
def foo(Closure cls) {}
def bar() { foo { 2 - it } }
''', 'Cannot find matching method int#minus(java.lang.Object)'
}
// GROOVY-7929
void testShouldDetectInvalidMethodUseWithinTraitWithCompileStaticAndSelfType() {
shouldFailWithMessages '''
class C1 {
def c1() { }
}
@groovy.transform.CompileStatic
@groovy.transform.SelfType(C1)
trait TT {
def foo() {
c2()
}
}
''', 'Cannot find matching method <UnionType:C1+TT>#c2'
}
void testGroovy5444() {
assertScript '''
def curr = { System.currentTimeMillis() }
5.times {
@ASTTest(phase=INSTRUCTION_SELECTION, value= {
assert node.getNodeMetaData(INFERRED_TYPE) == Long_TYPE
})
def t0 = curr()
100000.times {
"echo"
}
println (curr() - t0)
}'''
}
void testGroovy5487ReturnNull() {
assertScript '''
@ASTTest(phase=INSTRUCTION_SELECTION, value= {
assert node.getNodeMetaData(INFERRED_RETURN_TYPE) == null // null since 2.1.9
})
List getList() {
null
}
'''
}
void testGroovy5487ReturnNullWithExplicitReturn() {
assertScript '''
@ASTTest(phase=INSTRUCTION_SELECTION, value= {
assert node.getNodeMetaData(INFERRED_RETURN_TYPE) == null // null since 2.1.9
})
List getList() {
return null
}
'''
}
void testGroovy5487ReturnNullWithEmptyBody() {
assertScript '''
@ASTTest(phase=INSTRUCTION_SELECTION, value= {
assert node.getNodeMetaData(INFERRED_RETURN_TYPE) == null // null since 2.1.9
})
List getList() {
}
'''
}
void testGroovy7477NullGenericsType() {
assertScript '''
class L<E> extends ArrayList<E> {
boolean removeIf(Comparator<? super E> filter) { }
}
L<String> items = ['foo', 'bar'] as L<String>
items.removeIf({a, b -> 1} as Comparator<?>)
assert items
'''
}
void testGroovy5482ListsAndFlowTyping() {
assertScript '''
class StaticGroovy2 {
def bar() {
def foo = [new Date(), 1, new C()]
foo.add( 2 ) // Compiles
foo.add( new Date() )
foo.add( new C() )
foo = [new Date(), 1]
foo.add( 2 ) // Does not compile
}
}
class C{
}
new StaticGroovy2()'''
}
void testClosureDelegateThisOwner() {
assertScript '''
class A {
A that = this
void m() {
def cl = {
@ASTTest(phase=INSTRUCTION_SELECTION, value= {
assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'A'
})
def foo = this
assert this == that
}
cl()
cl = {
@ASTTest(phase=INSTRUCTION_SELECTION, value= {
assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'A'
})
def foo = delegate
assert delegate == that
}
cl()
cl = {
@ASTTest(phase=INSTRUCTION_SELECTION, value= {
assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'A'
})
def foo = owner
assert owner == that
}
}
}
new A().m()
'''
}
void testClosureDelegateThisOwnerUsingGetters() {
assertScript '''
class A {
A that = this
void m() {
def cl = {
@ASTTest(phase=INSTRUCTION_SELECTION, value= {
assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'A'
})
def foo = getThisObject()
assert getThisObject() == that
}
cl()
cl = {
@ASTTest(phase=INSTRUCTION_SELECTION, value= {
assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'A'
})
def foo = getDelegate()
assert getDelegate() == that
}
cl()
cl = {
@ASTTest(phase=INSTRUCTION_SELECTION, value= {
assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'A'
})
def foo = getOwner()
assert getOwner() == that
}
}
}
new A().m()
'''
}
// GROOVY-5616
void testAssignToGroovyObject() {
assertScript '''
class A {}
GroovyObject obj = new A()
'''
}
void testAssignJavaClassToGroovyObject() {
shouldFailWithMessages '''
GroovyObject obj = 'foo'
''', 'Cannot assign value of type java.lang.String to variable of type groovy.lang.GroovyObject'
}
void testCastToGroovyObject() {
assertScript '''
class A {}
GroovyObject obj = new A()
'''
}
void testAssignInnerClassToGroovyObject() {
assertScript '''
class A { static class B {} }
GroovyObject obj = new A.B()
'''
}
void testCastInnerClassToGroovyObject() {
assertScript '''
class A { static class B {} }
GroovyObject obj = (GroovyObject)new A.B()
'''
}
void testGroovyObjectInGenerics() {
assertScript '''
class A {}
List<? extends GroovyObject> list = new LinkedList<? extends GroovyObject>()
list.add(new A())
'''
}
// GROOVY-5656
void testShouldNotThrowAmbiguousMethodError() {
assertScript '''import groovy.transform.*
class Expr {}
class VarExpr extends Expr {}
class ArgList {
ArgList(Expr e1) { }
ArgList(Expr[] es) { }
}
class Bug4 {
void test() {
new ArgList(new VarExpr())
}
}
new Bug4().test()
'''
}
// GROOVY-5793
void testByteAsParameter() {
assertScript '''
void testMethod(java.lang.Byte param){
println(param)
}
void execute(){
testMethod(java.lang.Byte.valueOf("123"))
}
execute()'''
}
// GROOVY-5874-part-1
void testClosureSharedVariableInBinExp() {
shouldFailWithMessages '''
def sum = 0
def cl1 = { sum = sum + 1 }
def cl2 = { sum = new Date() }
''', 'A closure shared variable [sum] has been assigned with various types'
}
// GROOVY-5870
void testShouldNotThrowErrorIfTryingToCastToInterface() {
assertScript '''
Set tmp = null
List other = (List) tmp // should not complain because source and target are interfaces
'''
}
// GROOVY-5889
void testShouldNotGoIntoInfiniteLoop() {
assertScript '''
class Enclosing {
static class FMessage {
static enum LogLevel { finest, finer, fine, config, info, warning, severe }
LogLevel logLevel
}
}
new Enclosing()
'''
}
// GROOVY-5959
void testSwitchCaseShouldNotRemoveBreakStatements() {
assertScript '''
int test(Map<String, String> token) {
switch(token.type) {
case 'case one':
1
break
case 'case two':
2
break
default:
3
break
}
}
assert test([type:'case one']) == 1
assert test([type:'case two']) == 2
assert test([type:'default']) == 3
'''
}
void testShouldChooseFindMethodFromList() {
assertScript '''
class Mylist implements List<Object> {
int size() { }
boolean isEmpty() {}
boolean contains(final Object o) {}
Iterator iterator() {[].iterator()}
Object[] toArray() {}
Object[] toArray(final Object[] a) {}
boolean add(final Object e) {}
boolean remove(final Object o) {}
boolean containsAll(final Collection<?> c) {}
boolean addAll(final Collection c) {}
boolean addAll(final int index, final Collection c) {}
boolean removeAll(final Collection<?> c) {}
boolean retainAll(final Collection<?> c) {}
void clear() {}
Object get(final int index) {}
Object set(final int index, final Object element) {}
void add(final int index, final Object element) {}
Object remove(final int index) {}
int indexOf(final Object o) {}
int lastIndexOf(final Object o) {}
ListIterator listIterator() {}
ListIterator listIterator(final int index) {}
List subList(final int fromIndex, final int toIndex) {}
}
def whatthe(Mylist a) {
a.find { true }
}
whatthe(new Mylist())
'''
}
// GROOVY-6050
void testShouldAllowTypeConversion() {
assertScript '''
interface SomeInterface { void sayHello() }
void foo(Writer writer) {
if (writer instanceof SomeInterface) {
((SomeInterface)writer).sayHello()
}
}
foo(null)
'''
}
// GROOVY-6099
void testFlowTypingErrorWithIfElse() {
assertScript '''
def o = new Object()
boolean b = true
if (b) {
o = 1
} else {
@ASTTest(phase=INSTRUCTION_SELECTION, value={
assert node.getNodeMetaData(INFERRED_TYPE) == OBJECT_TYPE
})
def o2 = o
println (o2.toString())
}
'''
}
// GROOVY-6104
void testShouldResolveConstantFromInterfaceImplementedInSuperClass() {
assertScript '''
interface Foo {
public static int MY_CONST = 85
}
class FooImpl implements Foo {}
class Bar extends FooImpl {
void bar() {
assert MY_CONST == 85
}
}
new Bar().bar()
'''
}
// GROOVY-6098
void testUnresolvedPropertyReferencingIsBooleanMethod() {
assertScript '''
boolean isFoo() { true }
assert foo
'''
}
// GROOVY-6119
void testShouldCallConstructorWithMap() {
assertScript '''
class Foo {
String message
Foo(Map map) {
message = map.msg
}
}
def foo = new Foo(msg: 'bar')
assert foo.message == 'bar'
'''
}
// GROOVY-6119
void testShouldCallConstructorWithHashMap() {
assertScript '''
class Foo {
String message
Foo(HashMap map) {
message = map.msg
}
}
def foo = new Foo(msg: 'bar')
assert foo.message == 'bar'
'''
}
// GROOVY-6162
void testShouldConsiderThisInStaticContext() {
assertScript '''
class Foo {
static def staticMethod() {
@ASTTest(phase=INSTRUCTION_SELECTION,value={
def ift = node.rightExpression.getNodeMetaData(INFERRED_TYPE)
assert ift == CLASS_Type
assert ift.isUsingGenerics()
assert ift.genericsTypes[0].type.name == 'Foo'
})
def foo = this
this.classLoader
}
}
assert Foo.staticMethod() instanceof ClassLoader
'''
}
void testListToSet() {
assertScript '''
Set foo(List<Map.Entry> set) {
set.collect { Map.Entry entry -> entry.key }.toSet()
}
'''
}
void testConstructorNewInstance() {
assertScript '''import java.lang.reflect.Constructor
class Person {
String name
Person(String name) { this.name = name }
}
Constructor<Person> ctor = Person.getConstructor(String)
def p = ctor.newInstance('Bob')
assert p.name == 'Bob'
'''
}
void testOuterDotThisNotation() {
assertScript '''
class Outer {
int x
class Inner {
int foo() { 2*Outer.this.x }
}
int bar() {
new Inner().foo()
}
}
def o = new Outer(x:123)
assert o.bar() == 2*o.x
'''
}
// GROOVY-6965
void testShouldNotFailWithClassCastExceptionDuringCompilation() {
assertScript '''
interface Job {
Runnable getRunnable()
}
class Printer implements Job{
protected void execute() {
println "Printing"
}
public void acceptsRunnable(Runnable r){
r.run()
}
public Runnable getRunnable(){
acceptsRunnable(this.&execute) // OK
return this.&execute // compile error
}
}
Printer
'''
}
// GROOVY-6970
void testShouldBeAbleToChooseBetweenTwoEquivalentInterfaceMethods() {
assertScript '''
interface A { void m() }
interface B { void m() }
interface C extends A, B {}
class D {
D(C c) {
c.m()
}
}
class CImpl implements C {
void m() { }
}
new D(new CImpl())
'''
}
void testShouldBeAbleToChooseBetweenTwoEquivalentInterfaceMethodsVariant() {
assertScript '''
interface A { void m() }
interface B { void m() }
class C implements A,B {
void m() {}
}
class D {
D(C c) {
c.m()
}
}
new D(new C())
'''
}
void testShouldBeAbleToChooseBetweenTwoEquivalentInterfaceMethodsVariant2() {
assertScript '''
interface A { void m() }
interface B { void m() }
interface C extends A,B {}
class CImpl implements C, A,B {
void m() {}
}
class D {
D(C c) {
c.m()
}
}
new D(new CImpl())
'''
}
void testAmbiguousMethodResolutionGroovy6849() {
assertScript '''
interface ObservableList<E> extends List<E> {
public boolean addAll(E... elements)
}
public <E> ObservableList<E> wrap(List<E> list) { list as ObservableList }
ObservableList<String> tags = wrap(['groovy','programming'])
tags.addAll('bug')
'''
}
void testAmbiguousMethodResolutionGroovy7710NoArgsOverloaded() {
shouldFailWithMessages '''
Arrays.sort()
''', 'Reference to method is ambiguous. Cannot choose between '
}
void testAmbiguousMethodResolutionGroovy7711NoArgsCovariantOverride() {
assertScript '''
class A {}
class B {
Object m(Object[] args) {
new Object()
}
}
class C extends B {
A m(Object[] args) {
new A()
}
}
C c = new C()
A a = c.m()
'''
}
// GROOVY-6911
void testShouldNotThrowArrayIndexOfOutBoundsException() {
assertScript '''
class MyMap<T> extends LinkedHashMap<String, Object> { }
class C {
MyMap bar() { new MyMap() }
}
Map<String, Object> m = new C().bar()
List tmp = (List) m.get("some_key_here")
'''
}
// GROOVY-7416
void testMethodsFromInterfacesOfSuperClassesShouldBeVisible() {
assertScript '''
interface SomeInterface {
void someInterfaceMethod()
}
abstract class AbstractSuperClass implements SomeInterface {}
abstract class AbstractSubClass extends AbstractSuperClass {
void someMethod() {
someInterfaceMethod()
}
}
assert AbstractSubClass.name == 'AbstractSubClass'
'''
assertScript '''
interface SomeInterface { void foo() }
interface SomeOtherInterface { void bar() }
interface AnotherInterface extends SomeInterface, SomeOtherInterface {}
abstract class Parent implements AnotherInterface {}
abstract class Child extends Parent {
void baz() { foo(); bar() }
}
assert Child.name == 'Child'
'''
}
// GROOVY-7315
void testNamedArgConstructorSupportWithInnerClassesAndCS() {
assertScript '''
import groovy.transform.*
@ToString
class X {
int a
static X makeX() { new X(a:1) }
Y makeY() {
new Y(b:2)
}
@ToString
private class Y {
int b
@ToString
private class Z {
int c
}
Z makeZ() {
new Z(c:3)
}
}
}
assert X.makeX().toString() == 'X(1)'
assert X.makeX().makeY().toString() == 'X$Y(2)'
assert X.makeX().makeY().makeZ().toString() == 'X$Y$Z(3)'
'''
}
// GROOVY-8255 and GROOVY-8382
void testTargetTypingEmptyCollectionLiterals() {
assertScript '''
class Foo {
List<List<String>> items = [['x']]
def bar() {
List<String> result = []
List<String> selections = items.size() ? (items.get(0) ?: []) : items.size() > 1 ? items.get(1) : []
for (String selection: selections) {
result << selection
}
result
}
}
assert new Foo().bar() == ['x']
'''
assertScript '''
class Foo {
def bar() {
def items = [x:1]
Map<String, Integer> empty = [:]
Map<String, Integer> first = items ?: [:]
Map<String, Integer> second = first.isEmpty() ? [:] : [y:2]
[first, second]
}
}
assert new Foo().bar() == [[x:1], [y:2]]
'''
assertScript '''
import groovy.transform.*
@ToString(includeFields=true)
class Foo {
List<String> propWithGen = ['propWithGen'] ?: []
List propNoGen = ['propNoGen'] ?: []
private Map<String, Integer> fieldGen = [fieldGen:42] ?: [:]
def bar() {
this.propNoGen = ['notDecl'] ?: [] // not applicable here
List<String> localVar = ['localVar'] ?: []
localVar
}
}
def foo = new Foo()
assert foo.bar() == ['localVar']
assert foo.toString() == 'Foo([propWithGen], [notDecl], [fieldGen:42])'
'''
}
//GROOVY-8590
void testNestedMethodCallInferredTypeInReturnStmt() {
assertScript '''
class Source {
Object getValue() { '32' }
}
int m(Source src) {
return Integer.parseInt((String) src.getValue())
}
assert m(new Source()) == 32
'''
}
}