Skip to content

Commit cbb5a78

Browse files
committed
Infer Kotlin null-safety from type variables
This commit removes the constraint from type variables in PropertyResolver, JdbcOperations and RestOperations Kotlin extensions in order to get null-safety inferred from the type declared by the user. Closes gh-22687
1 parent 68a529b commit cbb5a78

File tree

6 files changed

+175
-124
lines changed

6 files changed

+175
-124
lines changed

spring-core/src/main/kotlin/org/springframework/core/env/PropertyResolverExtensions.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -39,8 +39,8 @@ operator fun PropertyResolver.get(key: String) : String? = getProperty(key)
3939
* @author Sebastien Deleuze
4040
* @since 5.1
4141
*/
42-
inline fun <reified T: Any?> PropertyResolver.getProperty(key: String) : T? =
43-
getProperty(key, T::class.java)
42+
inline fun <reified T> PropertyResolver.getProperty(key: String) : T =
43+
getProperty(key, T::class.java) as T
4444

4545
/**
4646
* Extension for [PropertyResolver.getRequiredProperty] providing a
@@ -49,5 +49,5 @@ inline fun <reified T: Any?> PropertyResolver.getProperty(key: String) : T? =
4949
* @author Sebastien Deleuze
5050
* @since 5.1
5151
*/
52-
inline fun <reified T: Any> PropertyResolver.getRequiredProperty(key: String) : T =
52+
inline fun <reified T> PropertyResolver.getRequiredProperty(key: String) : T =
5353
getRequiredProperty(key, T::class.java)

spring-core/src/test/kotlin/org/springframework/core/env/PropertyResolverExtensionsTests.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.core.env
1818

19+
import io.mockk.every
1920
import io.mockk.mockk
2021
import io.mockk.verify
2122
import org.junit.Test
@@ -27,22 +28,32 @@ import org.junit.Test
2728
*/
2829
class PropertyResolverExtensionsTests {
2930

30-
val propertyResolver = mockk<PropertyResolver>(relaxed = true)
31+
val propertyResolver = mockk<PropertyResolver>()
3132

3233
@Test
3334
fun `get operator`() {
35+
every { propertyResolver.getProperty("name") } returns "foo"
3436
propertyResolver["name"]
3537
verify { propertyResolver.getProperty("name") }
3638
}
3739

3840
@Test
3941
fun `getProperty extension`() {
42+
every { propertyResolver.getProperty("name", String::class.java) } returns "foo"
4043
propertyResolver.getProperty<String>("name")
4144
verify { propertyResolver.getProperty("name", String::class.java) }
4245
}
4346

47+
@Test
48+
fun `getProperty extension with nullable type`() {
49+
every { propertyResolver.getProperty("name", String::class.java) } returns null
50+
propertyResolver.getProperty<String?>("name")
51+
verify { propertyResolver.getProperty("name", String::class.java) }
52+
}
53+
4454
@Test
4555
fun `getRequiredProperty extension`() {
56+
every { propertyResolver.getRequiredProperty("name", String::class.java) } returns "foo"
4657
propertyResolver.getRequiredProperty<String>("name")
4758
verify { propertyResolver.getRequiredProperty("name", String::class.java) }
4859
}

spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensions.kt

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import java.sql.ResultSet
2424
* @author Mario Arias
2525
* @since 5.0
2626
*/
27-
inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String): T? =
28-
queryForObject(sql, T::class.java)
27+
inline fun <reified T> JdbcOperations.queryForObject(sql: String): T =
28+
queryForObject(sql, T::class.java) as T
2929

3030
/**
3131
* Extensions for [JdbcOperations.queryForObject] providing a RowMapper-like function
@@ -34,8 +34,8 @@ inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String): T? =
3434
* @author Mario Arias
3535
* @since 5.0
3636
*/
37-
fun <T : Any?> JdbcOperations.queryForObject(sql: String, vararg args: Any, function: (ResultSet, Int) -> T): T? =
38-
queryForObject(sql, RowMapper { resultSet, i -> function(resultSet, i) }, *args)
37+
inline fun <reified T> JdbcOperations.queryForObject(sql: String, vararg args: Any, crossinline function: (ResultSet, Int) -> T): T =
38+
queryForObject(sql, RowMapper { resultSet, i -> function(resultSet, i) }, *args) as T
3939

4040
/**
4141
* Extension for [JdbcOperations.queryForObject] providing a
@@ -44,8 +44,8 @@ fun <T : Any?> JdbcOperations.queryForObject(sql: String, vararg args: Any, func
4444
* @author Mario Arias
4545
* @since 5.0
4646
*/
47-
inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String, args: Array<out Any>, argTypes: IntArray): T? =
48-
queryForObject(sql, args, argTypes, T::class.java)
47+
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>, argTypes: IntArray): T? =
48+
queryForObject(sql, args, argTypes, T::class.java) as T
4949

5050
/**
5151
* Extension for [JdbcOperations.queryForObject] providing a
@@ -54,8 +54,8 @@ inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String, args: Ar
5454
* @author Mario Arias
5555
* @since 5.0
5656
*/
57-
inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String, args: Array<out Any>): T? =
58-
queryForObject(sql, args, T::class.java)
57+
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>): T? =
58+
queryForObject(sql, args, T::class.java) as T
5959

6060
/**
6161
* Extension for [JdbcOperations.queryForList] providing a `queryForList<Foo>("...")` variant.
@@ -64,7 +64,7 @@ inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String, args: Ar
6464
* @since 5.0
6565
*/
6666
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
67-
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String): List<T> =
67+
inline fun <reified T> JdbcOperations.queryForList(sql: String): List<T> =
6868
queryForList(sql, T::class.java)
6969

7070
/**
@@ -75,7 +75,7 @@ inline fun <reified T : Any> JdbcOperations.queryForList(sql: String): List<T> =
7575
* @since 5.0
7676
*/
7777
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
78-
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, args: Array<out Any>,
78+
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>,
7979
argTypes: IntArray): List<T> =
8080
queryForList(sql, args, argTypes, T::class.java)
8181

@@ -86,7 +86,7 @@ inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, args: Arra
8686
* @author Mario Arias
8787
* @since 5.0
8888
*/
89-
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, args: Array<out Any>): List<T> =
89+
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>): List<T> =
9090
queryForList(sql, args, T::class.java)
9191

9292
/**
@@ -96,9 +96,9 @@ inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, args: Arra
9696
* @author Mario Arias
9797
* @since 5.0
9898
*/
99-
inline fun <reified T : Any?> JdbcOperations.query(sql: String, vararg args: Any,
100-
crossinline function: (ResultSet) -> T): T? =
101-
query(sql, ResultSetExtractor { function(it) }, *args)
99+
inline fun <reified T> JdbcOperations.query(sql: String, vararg args: Any,
100+
crossinline function: (ResultSet) -> T): T =
101+
query(sql, ResultSetExtractor { function(it) }, *args) as T
102102

103103
/**
104104
* Extension for [JdbcOperations.query] providing a RowCallbackHandler-like function
@@ -117,5 +117,5 @@ fun JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet) ->
117117
* @author Mario Arias
118118
* @since 5.0
119119
*/
120-
fun <T : Any> JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet, Int) -> T): List<T> =
120+
fun <T> JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet, Int) -> T): List<T> =
121121
query(sql, RowMapper { rs, i -> function(rs, i) }, *args)

spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensionsTests.kt

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
package org.springframework.jdbc.core
1818

19+
import io.mockk.every
1920
import io.mockk.mockk
2021
import io.mockk.verify
2122
import org.junit.Assert.assertEquals
23+
import org.junit.Assert.assertNull
2224
import org.junit.Test
2325
import java.sql.*
2426

@@ -30,91 +32,96 @@ import java.sql.*
3032
*/
3133
class JdbcOperationsExtensionsTests {
3234

33-
val template = mockk<JdbcTemplate>(relaxed = true)
35+
val template = mockk<JdbcTemplate>()
36+
37+
val sql = "select age from customer where id = 3"
3438

3539
@Test
3640
fun `queryForObject with reified type parameters`() {
37-
val sql = "select age from customer where id = 3"
38-
template.queryForObject<Int>(sql)
39-
verify { template.queryForObject(sql, Integer::class.java) }
41+
every { template.queryForObject(sql, any<Class<Int>>()) } returns 2
42+
assertEquals(2, template.queryForObject<Int>(sql))
43+
verify { template.queryForObject(sql, any<Class<Int>>()) }
4044
}
4145

4246
@Test
4347
fun `queryForObject with RowMapper-like function`() {
44-
val sql = "select age from customer where id = ?"
45-
template.queryForObject(sql, 3) { rs: ResultSet, _: Int -> rs.getInt(1) }
48+
every { template.queryForObject(sql, any<RowMapper<Int>>(), any<Int>()) } returns 2
49+
assertEquals(2, template.queryForObject(sql, 3) { rs: ResultSet, _: Int -> rs.getInt(1) })
4650
verify { template.queryForObject(eq(sql), any<RowMapper<Int>>(), eq(3)) }
4751
}
4852

4953
@Test // gh-22682
5054
fun `queryForObject with nullable RowMapper-like function`() {
51-
val sql = "select age from customer where id = ?"
52-
template.queryForObject(sql, 3) { _, _ -> null as Int? }
55+
every { template.queryForObject(sql, any<RowMapper<Int>>(), 3) } returns null
56+
assertNull(template.queryForObject(sql, 3) { _, _ -> null as Int? })
5357
verify { template.queryForObject(eq(sql), any<RowMapper<Int?>>(), eq(3)) }
5458
}
5559

5660
@Test
5761
fun `queryForObject with reified type parameters and argTypes`() {
58-
val sql = "select age from customer where id = ?"
5962
val args = arrayOf(3)
6063
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
61-
template.queryForObject<Int>(sql, args, argTypes)
62-
verify { template.queryForObject(sql, args, argTypes, Integer::class.java) }
64+
every { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) } returns 2
65+
assertEquals(2, template.queryForObject<Int>(sql, args, argTypes))
66+
verify { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) }
6367
}
6468

6569
@Test
6670
fun `queryForObject with reified type parameters and args`() {
67-
val sql = "select age from customer where id = ?"
6871
val args = arrayOf(3)
69-
template.queryForObject<Int>(sql, args)
70-
verify { template.queryForObject(sql, args, Integer::class.java) }
72+
every { template.queryForObject(sql, args, any<Class<Int>>()) } returns 2
73+
assertEquals(2, template.queryForObject<Int>(sql, args))
74+
verify { template.queryForObject(sql, args, any<Class<Int>>()) }
7175
}
7276

7377
@Test
7478
fun `queryForList with reified type parameters`() {
75-
val sql = "select age from customer where id = 3"
76-
template.queryForList<Int>(sql)
77-
verify { template.queryForList(sql, Integer::class.java) }
79+
val list = listOf(1, 2, 3)
80+
every { template.queryForList(sql, any<Class<Int>>()) } returns list
81+
assertEquals(list, template.queryForList<Int>(sql))
82+
verify { template.queryForList(sql, any<Class<Int>>()) }
7883
}
7984

8085
@Test
8186
fun `queryForList with reified type parameters and argTypes`() {
82-
val sql = "select age from customer where id = ?"
87+
val list = listOf(1, 2, 3)
8388
val args = arrayOf(3)
8489
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
85-
template.queryForList<Int>(sql, args, argTypes)
86-
verify { template.queryForList(sql, args, argTypes, Integer::class.java) }
90+
every { template.queryForList(sql, args, argTypes, any<Class<Int>>()) } returns list
91+
assertEquals(list, template.queryForList<Int>(sql, args, argTypes))
92+
verify { template.queryForList(sql, args, argTypes, any<Class<Int>>()) }
8793
}
8894

8995
@Test
9096
fun `queryForList with reified type parameters and args`() {
91-
val sql = "select age from customer where id = ?"
97+
val list = listOf(1, 2, 3)
9298
val args = arrayOf(3)
99+
every { template.queryForList(sql, args, any<Class<Int>>()) } returns list
93100
template.queryForList<Int>(sql, args)
94-
verify { template.queryForList(sql, args, Integer::class.java) }
101+
verify { template.queryForList(sql, args, any<Class<Int>>()) }
95102
}
96103

97104
@Test
98105
fun `query with ResultSetExtractor-like function`() {
99-
val sql = "select age from customer where id = ?"
100-
template.query<Int>(sql, 3) { rs ->
106+
every { template.query(eq(sql), any<ResultSetExtractor<Int>>(), eq(3)) } returns 2
107+
assertEquals(2, template.query<Int>(sql, 3) { rs ->
101108
rs.next()
102109
rs.getInt(1)
103-
}
110+
})
104111
verify { template.query(eq(sql), any<ResultSetExtractor<Int>>(), eq(3)) }
105112
}
106113

107114
@Test // gh-22682
108115
fun `query with nullable ResultSetExtractor-like function`() {
109-
val sql = "select age from customer where id = ?"
110-
template.query<Int?>(sql, 3) { _ -> null }
116+
every { template.query(eq(sql), any<ResultSetExtractor<Int?>>(), eq(3)) } returns null
117+
assertNull(template.query<Int?>(sql, 3) { _ -> null })
111118
verify { template.query(eq(sql), any<ResultSetExtractor<Int?>>(), eq(3)) }
112119
}
113120

114121
@Suppress("RemoveExplicitTypeArguments")
115122
@Test
116123
fun `query with RowCallbackHandler-like function`() {
117-
val sql = "select age from customer where id = ?"
124+
every { template.query(sql, ofType<RowCallbackHandler>(), 3) } returns Unit
118125
template.query(sql, 3) { rs ->
119126
assertEquals(22, rs.getInt(1))
120127
}
@@ -123,10 +130,11 @@ class JdbcOperationsExtensionsTests {
123130

124131
@Test
125132
fun `query with RowMapper-like function`() {
126-
val sql = "select age from customer where id = ?"
127-
template.query(sql, 3) { rs, _ ->
133+
val list = listOf(1, 2, 3)
134+
every { template.query(sql, ofType<RowMapper<*>>(), 3) } returns list
135+
assertEquals(list, template.query(sql, 3) { rs, _ ->
128136
rs.getInt(1)
129-
}
137+
})
130138
verify { template.query(sql, ofType<RowMapper<*>>(), 3) }
131139
}
132140

0 commit comments

Comments
 (0)