blob: 645a6dece3eeb17a31d2f5a020315ed84060a66f [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 : assignments.
*/
class STCAssignmentTest extends StaticTypeCheckingTestCase {
void testAssignmentFailure() {
shouldFailWithMessages """
int x = new Object()
""", "Cannot assign value of type java.lang.Object to variable of type int"
}
void testAssignmentFailure2() {
shouldFailWithMessages """
Set set = new Object()
""", "Cannot assign value of type java.lang.Object to variable of type java.util.Set"
}
void testAssignmentFailure3() {
shouldFailWithMessages """
Set set = new Integer(2)
""", "Cannot assign value of type java.lang.Integer to variable of type java.util.Set"
}
void testIndirectAssignment() {
shouldFailWithMessages """
def o = new Object()
int x = o
""", "Cannot assign value of type java.lang.Object to variable of type int"
}
void testIndirectAssignment2() {
shouldFailWithMessages """
def o = new Object()
Set set = o
""", "Cannot assign value of type java.lang.Object to variable of type java.util.Set"
}
void testIndirectAssignment3() {
shouldFailWithMessages """
int x = 2
Set set = x
""", "Cannot assign value of type int to variable of type java.util.Set"
}
void testAssignmentToEnum() {
assertScript """
enum MyEnum { a, b, c }
MyEnum e = MyEnum.a
e = 'a' // string to enum is implicit
e = "${'a'}" // gstring to enum is implicit too
"""
}
void testAssignmentToEnumFailure() {
shouldFailWithMessages """
enum MyEnum { a, b, c }
MyEnum e = MyEnum.a
e = 1
""", "Cannot assign value of type int to variable of type MyEnum"
}
void testAssignmentToString() {
assertScript """
String str = new Object()
"""
}
void testAssignmentToBoolean() {
assertScript """
boolean test = new Object()
"""
}
void testAssignmentToBooleanClass() {
assertScript """
Boolean test = new Object()
"""
}
void testAssignmentToClass() {
assertScript """
Class test = 'java.lang.String'
"""
}
void testPlusEqualsOnInt() {
assertScript """
int i = 0
i += 1
"""
}
void testMinusEqualsOnInt() {
assertScript """
int i = 0
i -= 1
"""
}
void testIntPlusEqualsObject() {
shouldFailWithMessages """
int i = 0
i += new Object()
""", "Cannot find matching method int#plus(java.lang.Object)"
}
void testIntMinusEqualsObject() {
shouldFailWithMessages """
int i = 0
i -= new Object()
""", "Cannot find matching method int#minus(java.lang.Object)"
}
void testStringPlusEqualsString() {
assertScript """
String str = 'test'
str+='test2'
"""
}
void testPossibleLooseOfPrecision() {
shouldFailWithMessages '''
long a = Long.MAX_VALUE
int b = a
''', 'Possible loss of precision from long to int'
}
void testPossibleLooseOfPrecision2() {
assertScript '''
int b = 0L
'''
}
void testPossibleLooseOfPrecision3() {
assertScript '''
byte b = 127
'''
}
void testPossibleLooseOfPrecision4() {
shouldFailWithMessages '''
byte b = 128 // will not fit in a byte
''', 'Possible loss of precision from int to byte'
}
void testPossibleLooseOfPrecision5() {
assertScript '''
short b = 128
'''
}
void testPossibleLooseOfPrecision6() {
shouldFailWithMessages '''
short b = 32768 // will not fit in a short
''', 'Possible loss of precision from int to short'
}
void testPossibleLooseOfPrecision7() {
assertScript '''
int b = 32768L // mark it as a long, but it fits into an int
'''
}
void testPossibleLooseOfPrecision8() {
assertScript '''
int b = 32768.0f // mark it as a float, but it fits into an int
'''
}
void testPossibleLooseOfPrecision9() {
assertScript '''
int b = 32768.0d // mark it as a double, but it fits into an int
'''
}
void testPossibleLooseOfPrecision10() {
shouldFailWithMessages '''
int b = 32768.1d
''', 'Possible loss of precision from double to int'
}
void testCastIntToShort() {
assertScript '''
short s = (short) 0
'''
}
void testCastIntToFloat() {
assertScript '''
float f = (float) 1
'''
}
void testCompatibleTypeCast() {
assertScript '''
String s = 'Hello'
((CharSequence) s)
'''
}
void testIncompatibleTypeCast() {
shouldFailWithMessages '''
String s = 'Hello'
((Set) s)
''', 'Inconvertible types: cannot cast java.lang.String to java.util.Set'
}
void testIncompatibleTypeCastWithAsType() {
// If the user uses explicit type coercion, there's nothing we can do
assertScript '''
String s = 'Hello'
s as Set
'''
}
void testIncompatibleTypeCastWithTypeInference() {
shouldFailWithMessages '''
def s = 'Hello'
s = 1
((Set) s)
''', 'Inconvertible types: cannot cast int to java.util.Set'
}
void testArrayLength() {
assertScript '''
String[] arr = [1,2,3]
int len = arr.length
'''
}
void testMultipleAssignment1() {
assertScript '''
def (x,y) = [1,2]
assert x == 1
assert y == 2
'''
}
void testMultipleAssignmentWithExplicitTypes() {
assertScript '''
int x
int y
(x,y) = [1,2]
assert x == 1
assert y == 2
'''
}
void testMultipleAssignmentWithIncompatibleTypes() {
shouldFailWithMessages '''
List x
List y
(x,y) = [1,2]
''', 'Cannot assign value of type int to variable of type java.util.List'
}
void testMultipleAssignmentWithoutEnoughArgs() {
shouldFailWithMessages '''
int x
int y
(x,y) = [1]
''', 'Incorrect number of values. Expected:2 Was:1'
}
void testMultipleAssignmentTooManyArgs() {
assertScript '''
int x
int y
(x,y) = [1,2,3]
assert x == 1
assert y == 2
'''
}
void testMultipleAssignmentFromVariable() {
shouldFailWithMessages '''
def list = [1,2,3]
def (x,y) = list
''', 'Multiple assignments without list expressions on the right hand side are unsupported in static type checking mode'
}
void testAssignmentToInterface() {
assertScript '''
Serializable ser = 'Hello'
'''
}
void testAssignmentToIncompatibleInterface() {
shouldFailWithMessages '''
Collection ser = 'Hello'
''', 'Cannot assign value of type java.lang.String to variable of type java.util.Collection'
}
void testTernaryOperatorAssignmentShouldFailBecauseOfIncompatibleGenericTypes() {
shouldFailWithMessages '''
List<Integer> foo = true?new LinkedList<String>():new LinkedList<Integer>();
''', 'Incompatible generic argument types. Cannot assign java.util.LinkedList <? extends java.io.Serializable <? extends java.io.Serializable>> to: java.util.List <Integer>'
}
void testCastStringToChar() {
assertScript '''
char c = 'a'
'''
}
void testCastStringLongerThan1CharToChar() {
shouldFailWithMessages '''
char c = 'aa'
''','Cannot assign value of type java.lang.String to variable of type char'
}
void testCastNullToChar() {
shouldFailWithMessages '''
char c = null
''', 'Cannot assign value of type java.lang.Object to variable of type char'
}
void testCastNullToBoolean() {
// GROOVY-6577
assertScript '''
boolean c = null
assert c == false
'''
}
void testCastNullToBooleanWithExplicitCast() {
// GROOVY-6577
assertScript '''
boolean c = (boolean) null
assert c == false
'''
}
void testCastStringToCharacter() {
assertScript '''
Character c = 'a'
'''
}
void testCastStringLongerThan1CharToCharacter() {
shouldFailWithMessages '''
Character c = 'aa'
''','Cannot assign value of type java.lang.String to variable of type java.lang.Character'
}
void testAssignNullToCharacter() {
assertScript '''
Character c = null
'''
}
void testCastStringToCharWithCast() {
assertScript '''
def c = (char) 'a'
'''
}
void testCastCharToByte() {
assertScript '''
void foo(char c) {
byte b = (byte) c
}
'''
}
void testCastCharToInt() {
assertScript '''
void foo(char c) {
int b = (int) c
}
'''
}
void testCastStringLongerThan1ToCharWithCast() {
shouldFailWithMessages '''
def c = (char) 'aa'
''', 'Inconvertible types: cannot cast java.lang.String to char'
}
void testCastNullToCharWithCast() {
shouldFailWithMessages '''
def c = (char) null
''', 'Inconvertible types: cannot cast java.lang.Object to char'
}
void testCastStringToCharacterWithCast() {
assertScript '''
def c = (Character) 'a'
'''
}
void testCastStringLongerThan1ToCharacterWithCast() {
shouldFailWithMessages '''
def c = (Character) 'aa'
''', 'Inconvertible types: cannot cast java.lang.String to java.lang.Character'
}
void testCastNullToCharacterWithCast() {
assertScript '''
def c = (Character) null
'''
}
void testCastObjectToSubclass() {
assertScript '''
Object o = null
try {
((Integer)o).intValue()
} catch (NullPointerException e) {
}
'''
}
void testIfElseBranch() {
shouldFailWithMessages '''
def x
def y = 'foo'
if (y) {
x = new HashSet()
} else {
x = '123'
}
x.toInteger()
''', 'Cannot find matching method java.io.Serializable#toInteger()'
}
void testIfElseBranchParameter() {
shouldFailWithMessages '''
def foo(x) {
def y = 'foo'
if (y) {
x = new HashSet()
} else {
x = '123'
}
x.toInteger()
}
foo('bar')
''', 'Cannot find matching method java.lang.Object#toInteger()'
}
void testIfOnly() {
shouldFailWithMessages '''
def x = '123'
def y = 'foo'
if (y) {
x = new HashSet()
}
x.toInteger()
''', 'Cannot find matching method java.io.Serializable#toInteger()'
}
void testIfOnlyParameter() {
shouldFailWithMessages '''
def foo(x) {
def y = 'foo'
if (y) {
x = new HashSet()
assert x.isEmpty()
}
x.toInteger()
}
foo('123')
''', 'Cannot find matching method java.lang.Object#toInteger()'
}
void testIfWithCommonInterface() {
assertScript '''
interface Foo { void foo() }
class A implements Foo { void foo() { println 'A' } }
class B implements Foo { void foo() { println 'B' } }
def x = new A()
def y = 'foo'
if (y) {
x = new B()
}
x.foo()
'''
}
void testForLoopWithNewAssignment() {
shouldFailWithMessages '''
def x = '123'
for (int i=0; i<5;i++) { x = new HashSet() }
x.toInteger()
''', 'Cannot find matching method java.io.Serializable#toInteger()'
}
void testWhileLoopWithNewAssignment() {
shouldFailWithMessages '''
def x = '123'
while (false) { x = new HashSet() }
x.toInteger()
''', 'Cannot find matching method java.io.Serializable#toInteger()'
}
void testTernaryWithNewAssignment() {
shouldFailWithMessages '''
def x = '123'
def cond = false
cond?(x = new HashSet()):3
x.toInteger()
''', 'Cannot find matching method java.io.Serializable#toInteger()'
}
void testFloatSub() {
assertScript '''
float x = 1.0f
float y = 1.0f
float z = x-y
'''
}
void testDoubleMinusInt() {
assertScript '''
double m() {
double a = 10d
int b = 1
double c = a-b
}
assert m()==9d
'''
}
void testDoubleMinusFloat() {
assertScript '''
double m() {
double a = 10d
float b = 1f
double c = a-b
}
assert m()==9d
'''
}
void testBigDecimalSub() {
assertScript '''
BigDecimal m() {
BigDecimal a = 10
BigDecimal b = 10
BigDecimal c = a-b
}
assert m()==0
assert m().getClass() == BigDecimal
'''
}
void testBigDecimalMinusDouble() {
assertScript '''
BigDecimal m() {
BigDecimal a = 10
double b = 10d
BigDecimal c = a-b
}
assert m()==0
assert m().getClass() == BigDecimal
'''
}
void testFloatSum() {
assertScript '''
float x = 1.0f
float y = 1.0f
float z = x+y
'''
}
void testDoublePlusInt() {
assertScript '''
double m() {
double a = 10d
int b = 1
double c = a+b
}
assert m()==11d
'''
}
void testDoublePlusFloat() {
assertScript '''
double m() {
double a = 10d
float b = 1f
double c = a+b
}
assert m()==11d
'''
}
void testBigDecimalSum() {
assertScript '''
BigDecimal m() {
BigDecimal a = 10
BigDecimal b = 10
BigDecimal c = a+b
}
assert m()==20
assert m().getClass() == BigDecimal
'''
}
void testBigDecimalPlusDouble() {
assertScript '''
BigDecimal m() {
BigDecimal a = 10
double b = 10d
BigDecimal c = a+b
}
assert m()==20
assert m().getClass() == BigDecimal
'''
}
void testBigIntegerAssignment() {
assertScript '''
BigInteger bigInt = 6666666666666666666666666666666666666
assert bigInt.toString()=='6666666666666666666666666666666666666'
assert bigInt.class == BigInteger
'''
}
void testBigIntegerSum() {
assertScript '''
BigInteger a = 6666666666666666666666666666666666666
BigInteger b = 6666666666666666666666666666666666666
BigInteger c = a + b
assert c.toString()=='13333333333333333333333333333333333332'
assert c.class == BigInteger
'''
}
void testBigIntegerSub() {
assertScript '''
BigInteger a = 6666666666666666666666666666666666666
BigInteger b = 6666666666666666666666666666666666666
BigInteger c = a - b
assert c.toString()=='0'
assert c.class == BigInteger
'''
}
void testBigIntegerMult() {
assertScript '''
BigInteger a = 6666666666666666666666666666666666666
BigInteger b = 2
BigInteger c = a * b
assert c.toString()=='13333333333333333333333333333333333332'
assert c.class == BigInteger
'''
}
void testBigIntegerMultDouble() {
assertScript '''
BigInteger a = 333
double b = 2d
BigDecimal c = a * b
assert c == 666
assert c.getClass() == BigDecimal
'''
shouldFailWithMessages '''
BigInteger a = 333
double b = 2d
BigInteger c = a * b
''', 'Cannot assign value of type java.math.BigDecimal to variable of type java.math.BigInteger'
}
void testBigIntegerMultInteger() {
assertScript '''
BigInteger a = 333
int b = 2
BigDecimal c = a * b
assert c == 666
assert c.getClass() == BigDecimal
'''
}
//GROOVY-6435
void testBigDecAndBigIntSubclass() {
assertScript '''
class MyDecimal extends BigDecimal {
public MyDecimal(String s) {super(s)}
}
class MyInteger extends BigInteger {
public MyInteger(String s) {super(s)}
}
BigDecimal d = new MyDecimal("3.0")
BigInteger i = new MyInteger("3")
'''
}
void testPostfixOnInt() {
assertScript '''
int i = 0
i++
'''
assertScript '''
int i = 0
i--
'''
}
void testPostfixOnDate() {
assertScript '''
Date d = new Date()
d++
'''
assertScript '''
Date d = new Date()
d--
'''
}
void testPostfixOnObject() {
shouldFailWithMessages '''
Object o = new Object()
o++
''', 'Cannot find matching method java.lang.Object#next()'
shouldFailWithMessages '''
Object o = new Object()
o--
''', 'Cannot find matching method java.lang.Object#previous()'
}
void testPrefixOnInt() {
assertScript '''
int i = 0
++i
'''
assertScript '''
int i = 0
--i
'''
}
void testPrefixOnDate() {
assertScript '''
Date d = new Date()
++d
'''
assertScript '''
Date d = new Date()
--d
'''
}
void testPrefixOnObject() {
shouldFailWithMessages '''
Object o = new Object()
++o
''', 'Cannot find matching method java.lang.Object#next()'
shouldFailWithMessages '''
Object o = new Object()
--o
''', 'Cannot find matching method java.lang.Object#previous()'
}
void testAssignArray() {
assertScript '''
String[] src = ['a','b','c']
Object[] arr = src
'''
}
void testCastArray() {
assertScript '''
List<String> src = ['a','b','c']
(String[]) src.toArray(src as String[])
'''
}
void testIncompatibleCastArray() {
shouldFailWithMessages '''
String[] src = ['a','b','c']
(Set[]) src
''', 'Inconvertible types: cannot cast java.lang.String[] to java.util.Set[]'
}
void testIncompatibleToArray() {
shouldFailWithMessages '''
(Set[]) ['a','b','c'].toArray(new String[3])
''', 'Inconvertible types: cannot cast java.lang.String[] to java.util.Set[]'
}
// GROOVY-5535
void testAssignToNullInsideIf() {
assertScript '''
Date foo() {
Date result = new Date()
if (true) {
result = null
}
return result
}
assert foo() == null
'''
}
// GROOVY-5798
void testShouldNotThrowConversionError() {
assertScript '''
char m( int v ) {
char c = (char)v
c
}
println m( 65 )
'''
}
// GROOVY-6575
void testAssignmentOfObjectToArrayShouldFail() {
shouldFailWithMessages '''
Object o
int[] array = o
''', 'Cannot assign value of type java.lang.Object to variable of type int[]'
}
// GROOVY-7015
void testAssingmentToSuperclassFieldWithDifferingGenerics() {
assertScript '''
class Base {}
class Derived extends Base {
public String sayHello() { "hello"}
}
class GBase<T extends Base> {
T myVar;
}
class GDerived extends GBase<Derived> {
GDerived() { myVar = new Derived(); }
public String method() {myVar.sayHello()}
}
GDerived d = new GDerived();
assert d.method() == "hello"
'''
}
//GROOVY-8157
void testFlowTypingAfterParameterAssignment() {
assertScript '''
class A {}
class B extends A { def bbb() { 42 } }
def fooParameterAssignment(A a) {
a = new B()
a.bbb()
}
assert fooParameterAssignment(null) == 42
'''
}
void testIntegerArraySmartType() {
assertScript '''
def m() {
def a = 1
Integer[] b = [a]
}
'''
}
void testIntegerSecondDimArraySmartType() {
assertScript '''
def m() {
def a = new int[5]
int[][] b = [a]
}
'''
}
void testMultiAssign() {
assertScript '''
def m() {
def row = ["", "", ""]
def (left, right) = [row[0], row[1]]
left.toUpperCase()
}
'''
}
// GROOVY-8220
void testFlowTypingParameterTempTypeAssignmentTracking() {
assertScript '''
class Foo {
CharSequence makeEnv( env, StringBuilder result = new StringBuilder() ) {
if (env instanceof File) {
env = env.toPath()
}
if (env instanceof String && env.contains('=')) {
result << 'export ' << env << ';'
}
return result.toString()
}
}
assert new Foo().makeEnv('X=1') == 'export X=1;'
'''
// GROOVY-8237
assertScript '''
class Foo {
String parse(Reader reader) {
if (reader == null)
reader = new BufferedReader(reader)
int i = reader.read()
return (i != -1) ? 'bar' : 'baz'
}
}
assert new Foo().parse(new StringReader('foo')) == 'bar'
'''
}
void testFlowTypingParameterTempTypeAssignmentTrackingWithGenerics() {
assertScript '''
class M {
Map<String, List<Object>> mvm = new HashMap<String, List<Object>>()
void setProperty(String name, value) {
if (value instanceof File) {
value = new File(value, 'bar.txt')
}
else if (value instanceof URL) {
value = value.toURI()
}
else if (value instanceof InputStream) {
value = new BufferedInputStream(value)
}
else if (value instanceof GString) {
value = value.toString()
}
if (mvm[name]) {
mvm[name].add value
} else {
mvm.put(name, [value])
}
}
}
new M().setProperty('foo', 'bar')
'''
}
void testNarrowingConversion() {
assertScript '''
interface A1{}
interface A2 extends A1{}
class C1 implements A1{}
def m(A2 a2) {
C1 c1 = (C1) a2
}
'''
}
void testFinalNarrowingConversion() {
shouldFailWithMessages '''
interface A1{}
interface A2 extends A1{}
final class C1 implements A1{}
def m(A2 a2) {
C1 c1 = (C1) a2
}
''', "Inconvertible types: cannot cast A2 to C1"
}
}