Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Smithy default trait #857

Merged
merged 38 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0f155c2
Check for DefaultTrait and update nullability index check mode to `CL…
lauzadis May 23, 2023
51f8dbb
Add a few tests
lauzadis May 23, 2023
1aff46c
Add a few more tests
lauzadis May 23, 2023
4aebfe7
Full test suite passing
lauzadis May 23, 2023
528a21d
ktlint
lauzadis May 24, 2023
24f6c67
Remove duplicated default trait parsing
lauzadis May 24, 2023
292dc53
Add a changelog
lauzadis May 24, 2023
1284dd2
Suppress warning
lauzadis May 24, 2023
de93d87
Update streaming blob test to IDLv2 (making member required)
lauzadis May 24, 2023
fc2ff86
Revert
lauzadis May 24, 2023
10cb7b0
Special-case BlobShape empty-string default value
lauzadis May 24, 2023
345b411
Support defaulting empty string
lauzadis May 24, 2023
09719fe
Generic support for string node
lauzadis May 24, 2023
2f84ed7
Test null override
lauzadis May 25, 2023
6b5e7e4
Rename `boxed` to `nullable`
lauzadis May 25, 2023
cf9d353
Handle null default precedence
lauzadis May 25, 2023
e66c579
ktlint
lauzadis May 25, 2023
42ad105
Don't remove default value key, just put `null`
lauzadis May 25, 2023
ccff67e
Merge branch 'main' of github.com:awslabs/smithy-kotlin into feat-def…
lauzadis May 25, 2023
84f69c7
refactor to `when`
lauzadis May 25, 2023
f419475
Merge branch 'main' of github.com:awslabs/smithy-kotlin into feat-def…
lauzadis May 26, 2023
eb084e9
Update KDocs
lauzadis May 30, 2023
781ac4f
Set default value for numbers, update Float's default value to 0f
lauzadis May 30, 2023
ea1fbc1
Merge branch 'main' of github.com:awslabs/smithy-kotlin into feat-def…
lauzadis Jun 5, 2023
3a638e1
Add a default value type
lauzadis Jun 5, 2023
939ce26
ktlint
lauzadis Jun 5, 2023
4a3afb5
Rename box->nullable
lauzadis Jun 6, 2023
8a51691
Revert nullability index check mode
lauzadis Jun 7, 2023
defa0ce
Fix test
lauzadis Jun 7, 2023
aee25a0
Fix test
lauzadis Jun 7, 2023
42bbd66
Merge branch 'main' of github.com:awslabs/smithy-kotlin into feat-def…
lauzadis Jun 7, 2023
367d8d5
Rename boxed->nullable
lauzadis Jun 7, 2023
80e2ce8
Use `String.dq()`
lauzadis Jun 7, 2023
f8ccaf9
Default to Smithy IDLv2 in tests
lauzadis Jun 7, 2023
844b970
Add extension method for default value type
lauzadis Jun 7, 2023
137a013
Add kdocs for DefaultValueType enum
lauzadis Jun 7, 2023
1988148
Merge branch 'main' of github.com:awslabs/smithy-kotlin into feat-def…
lauzadis Jun 16, 2023
c0ca0ee
Assert isNullable, add tests for `@clientOptional` and `@input`
lauzadis Jun 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changes/d3ec877c-68c6-4c21-881f-8767d1f87e1c.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "d3ec877c-68c6-4c21-881f-8767d1f87e1c",
"type": "feature",
"description": "Support Smithy default trait",
"issues": [
"https://github.com/awslabs/smithy-kotlin/issues/718"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ import java.net.URL

/**
* Unless necessary to deviate for test reasons, the following literals should be used in test models:
* smithy version: "1"
* model version: "1.0.0"
* smithy version: "2"
* model version: "2.0.0"
* namespace: TestDefault.NAMESPACE
* service name: "Test"
*/
object TestModelDefault {
const val SMITHY_IDL_VERSION = "1"
const val MODEL_VERSION = "1.0.0"
const val SMITHY_IDL_VERSION = "2"
const val MODEL_VERSION = "2.0.0"
const val NAMESPACE = "com.test"
const val SERVICE_NAME = "Test"
const val SDK_ID = "Test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import software.amazon.smithy.codegen.core.*
import software.amazon.smithy.kotlin.codegen.KotlinSettings
import software.amazon.smithy.kotlin.codegen.lang.kotlinReservedWords
import software.amazon.smithy.kotlin.codegen.model.*
import software.amazon.smithy.kotlin.codegen.utils.dq
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.knowledge.NullableIndex
import software.amazon.smithy.model.shapes.*
import software.amazon.smithy.model.traits.DefaultTrait
import software.amazon.smithy.model.traits.SparseTrait
import software.amazon.smithy.model.traits.StreamingTrait
import java.util.logging.Logger
Expand Down Expand Up @@ -75,31 +77,29 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli

override fun longShape(shape: LongShape): Symbol = numberShape(shape, "Long", "0L")

override fun floatShape(shape: FloatShape): Symbol = numberShape(shape, "Float", "0.0f")
override fun floatShape(shape: FloatShape): Symbol = numberShape(shape, "Float", "0f")

override fun doubleShape(shape: DoubleShape): Symbol = numberShape(shape, "Double", "0.0")

private fun numberShape(shape: Shape, typeName: String, defaultValue: String = "0"): Symbol =
createSymbolBuilder(shape, typeName, namespace = "kotlin")
.defaultValue(defaultValue)
.build()
createSymbolBuilder(shape, typeName, namespace = "kotlin").defaultValue(defaultValue).build()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: so all number shapes having a INFERRED default value means the shape knows what the value would be, but the shape can be nullable too, and it's the nullable check that'll decide how the code is generated, and if it is not nullable, only then that defaultValue is relevant? Trying to understand the motivation for maintaining INFERRED v/s MODELED.

Copy link
Contributor Author

@lauzadis lauzadis Jun 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The motivation behind INFERRED / MODELED is because we're using .defaultValue() in two ways now. The previous way is inferring that numbers and booleans have a default value (0, 0f, 0.0, false). The new way we use .defaultValue() is when it's modeled on a shape.

If a shape has an INFERRED default value but it's nullable, we set it to null. If a shape has a MODELED default value but it's nullable, we will use the default value.


override fun bigIntegerShape(shape: BigIntegerShape?): Symbol = createBigSymbol(shape, "BigInteger")

override fun bigDecimalShape(shape: BigDecimalShape?): Symbol = createBigSymbol(shape, "BigDecimal")

private fun createBigSymbol(shape: Shape?, symbolName: String): Symbol =
createSymbolBuilder(shape, symbolName, namespace = "java.math", boxed = true).build()
createSymbolBuilder(shape, symbolName, namespace = "java.math", nullable = true).build()

override fun stringShape(shape: StringShape): Symbol = if (shape.isEnum) {
createEnumSymbol(shape)
} else {
createSymbolBuilder(shape, "String", boxed = true, namespace = "kotlin").build()
createSymbolBuilder(shape, "String", nullable = true, namespace = "kotlin").build()
}

private fun createEnumSymbol(shape: Shape): Symbol {
val namespace = "$rootNamespace.model"
return createSymbolBuilder(shape, shape.defaultName(service), namespace, boxed = true)
return createSymbolBuilder(shape, shape.defaultName(service), namespace, nullable = true)
.definitionFile("${shape.defaultName(service)}.kt")
.build()
}
Expand All @@ -110,7 +110,7 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
override fun structureShape(shape: StructureShape): Symbol {
val name = shape.defaultName(service)
val namespace = "$rootNamespace.model"
val builder = createSymbolBuilder(shape, name, namespace, boxed = true)
val builder = createSymbolBuilder(shape, name, namespace, nullable = true)
.definitionFile("$name.kt")

// add a reference to each member symbol
Expand Down Expand Up @@ -149,7 +149,7 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
override fun listShape(shape: ListShape): Symbol {
val reference = toSymbol(shape.member)
val valueType = if (shape.hasTrait<SparseTrait>()) "${reference.name}?" else reference.name
return createSymbolBuilder(shape, "List<$valueType>", boxed = true)
return createSymbolBuilder(shape, "List<$valueType>", nullable = true)
.addReferences(reference)
.putProperty(SymbolProperty.MUTABLE_COLLECTION_FUNCTION, "mutableListOf<$valueType>")
.putProperty(SymbolProperty.IMMUTABLE_COLLECTION_FUNCTION, "listOf<$valueType>")
Expand All @@ -160,7 +160,7 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
val reference = toSymbol(shape.value)
val valueType = if (shape.hasTrait<SparseTrait>()) "${reference.name}?" else reference.name

return createSymbolBuilder(shape, "Map<String, $valueType>", boxed = true)
return createSymbolBuilder(shape, "Map<String, $valueType>", nullable = true)
.addReferences(reference)
.putProperty(SymbolProperty.MUTABLE_COLLECTION_FUNCTION, "mutableMapOf<String, $valueType>")
.putProperty(SymbolProperty.IMMUTABLE_COLLECTION_FUNCTION, "mapOf<String, $valueType>")
Expand All @@ -172,11 +172,17 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
val targetShape =
model.getShape(shape.target).orElseThrow { CodegenException("Shape not found: ${shape.target}") }

val targetSymbol = if (nullableIndex.isMemberNullable(shape, NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1_NO_INPUT)) {
toSymbol(targetShape).toBuilder().boxed().build()
} else {
toSymbol(targetShape)
}
val targetSymbol = toSymbol(targetShape)
.toBuilder()
.apply {
if (nullableIndex.isMemberNullable(shape, NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1_NO_INPUT)) nullable()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given kotlin SDK is not GA, could NullableIndex.CheckMode.CLIENT be used?

And note, nullableIndex.isMemberNullable(shape) uses CheckMode.CLIENT.

Copy link
Contributor Author

@lauzadis lauzadis Jun 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're stuck using CLIENT_ZERO_VALUE_V1_NO_INPUT until S3 makes all their numbers and booleans nullable. We tried changing it in this PR but faced some issues with @default and nullability on things like PutObjectRequest's bucketKeyEnabled.

Internal reference: P83747977

We can hold off on merging this PR until it's resolved internally, and then I can update to use CheckMode.CLIENT

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. if this needs to be merged sooner for some reason, as long as there's tracking / TODO somewhere to change to CLIENT that sounds fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spoke with the team, we'll merge the PR now and add a tracking item to update to CLIENT


shape.getTrait<DefaultTrait>()?.let {
defaultValue(it.getDefaultValue(targetShape), DefaultValueType.MODELED)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so if a member has @default(null) in the model ("to explicitly indicate that the member has no default value or to override the default value requirement of a targeted shape."), this will be treated as having a MODELED default, even though semantically it makes the member nullable. So it would say nullable=true and defaultValueType=MODELED? I'm still trying to understand how MODELED is used, but just checking if this is ok/intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@default(null) will not make the member nullable, it will just override any default value which has been potentially set by a target shape. the only thing that makes members nullable is the nullability index provided by Smithy. My other response has an explanation of INFERRED vs. MODELED

}
}
.build()

// figure out if we are referencing an event stream or not.
// NOTE: unlike blob streams we actually re-use the target (union) shape which is why we can't do this
// when visiting a unionShape() like we can for blobShape()
Expand All @@ -197,9 +203,18 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
}
}

private fun DefaultTrait.getDefaultValue(targetShape: Shape): String? = when {
toNode().toString() == "null" || targetShape is BlobShape && toNode().toString() == "" -> null
toNode().isNumberNode -> getDefaultValueForNumber(targetShape, toNode().toString())
toNode().isArrayNode -> "listOf()"
toNode().isObjectNode -> "mapOf()"
toNode().isStringNode -> toNode().toString().dq()
else -> toNode().toString()
}

override fun timestampShape(shape: TimestampShape?): Symbol {
val dependency = KotlinDependency.CORE
return createSymbolBuilder(shape, "Instant", boxed = true)
return createSymbolBuilder(shape, "Instant", nullable = true)
.namespace("${dependency.namespace}.time", ".")
.addDependency(dependency)
.build()
Expand All @@ -208,7 +223,7 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
override fun blobShape(shape: BlobShape): Symbol = if (shape.hasTrait<StreamingTrait>()) {
RuntimeTypes.Core.Content.ByteStream.asNullable()
} else {
createSymbolBuilder(shape, "ByteArray", boxed = true, namespace = "kotlin").build()
createSymbolBuilder(shape, "ByteArray", nullable = true, namespace = "kotlin").build()
}

override fun documentShape(shape: DocumentShape?): Symbol =
Expand All @@ -217,7 +232,7 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
override fun unionShape(shape: UnionShape): Symbol {
val name = shape.defaultName(service)
val namespace = "$rootNamespace.model"
val builder = createSymbolBuilder(shape, name, namespace, boxed = true)
val builder = createSymbolBuilder(shape, name, namespace, nullable = true)
.definitionFile("$name.kt")

// add a reference to each member symbol
Expand All @@ -243,16 +258,23 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
/**
* Creates a symbol builder for the shape with the given type name in the root namespace.
*/
private fun createSymbolBuilder(shape: Shape?, typeName: String, boxed: Boolean = false): Symbol.Builder {
private fun createSymbolBuilder(shape: Shape?, typeName: String, nullable: Boolean = false): Symbol.Builder {
val builder = Symbol.builder()
.putProperty(SymbolProperty.SHAPE_KEY, shape)
.name(typeName)
if (boxed) {
builder.boxed()
if (nullable) {
builder.nullable()
}
return builder
}

private fun getDefaultValueForNumber(shape: Shape, value: String) = when (shape) {
is LongShape -> "${value}L"
is FloatShape -> "${value}f"
is DoubleShape -> if (value.matches("[0-9]*\\.[0-9]+".toRegex())) value else "$value.0"
else -> value
}

/**
* Creates a symbol builder for the shape with the given type name in a child namespace relative
* to the root namespace e.g. `relativeNamespace = bar` with a root namespace of `foo` would set
Expand All @@ -262,8 +284,8 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
shape: Shape?,
typeName: String,
namespace: String,
boxed: Boolean = false,
): Symbol.Builder = createSymbolBuilder(shape, typeName, boxed).namespace(namespace, ".")
nullable: Boolean = false,
): Symbol.Builder = createSymbolBuilder(shape, typeName, nullable).namespace(namespace, ".")
}

// Add a reference and it's children
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ class KotlinPropertyFormatter(
is Symbol -> {
writer.addImport(type)
var formatted = if (fullyQualifiedNames) type.fullName else type.name
if (includeNullability && type.isBoxed) {
if (includeNullability && type.isNullable) {
formatted += "?"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ open class SymbolBuilder {
fun build(): Symbol {
builder.name(name)
if (nullable) {
builder.boxed()
builder.nullable()
}
builder.putProperty(SymbolProperty.IS_EXTENSION, isExtension)
if (objectRef != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ object SymbolProperty {
// The key that holds the default value for a type (symbol) as a string
const val DEFAULT_VALUE_KEY: String = "defaultValue"

// Boolean property indicating this symbol should be boxed
const val BOXED_KEY: String = "boxed"
// The key that holds the type of default value
const val DEFAULT_VALUE_TYPE_KEY: String = "defaultValueType"

// Boolean property indicating this symbol is nullable
const val NULLABLE_KEY: String = "nullable"

// the original shape the symbol was created from
const val SHAPE_KEY: String = "shape"
Expand Down Expand Up @@ -47,21 +50,21 @@ object SymbolProperty {
}

/**
* Test if a symbol is boxed
* Test if a symbol is nullable
*/
val Symbol.isBoxed: Boolean
get() = getProperty(SymbolProperty.BOXED_KEY).map {
val Symbol.isNullable: Boolean
get() = getProperty(SymbolProperty.NULLABLE_KEY).map {
when (it) {
is Boolean -> it
else -> false
}
}.orElse(false)

/**
* Test if a symbol is not boxed
* Test if a symbol is not nullable
*/
val Symbol.isNotBoxed: Boolean
get() = !isBoxed
val Symbol.isNotNullable: Boolean
get() = !isNullable

enum class PropertyTypeMutability {
/**
Expand All @@ -82,6 +85,21 @@ enum class PropertyTypeMutability {
}
}

enum class DefaultValueType {
/**
* A default value which has been inferred, such as 0f for floats and false for booleans
*/
INFERRED,

/**
* A default value which has been modeled using Smithy's default trait.
*/
MODELED,
}

val Symbol.defaultValueType: DefaultValueType?
get() = getProperty(SymbolProperty.DEFAULT_VALUE_TYPE_KEY, DefaultValueType::class.java).getOrNull()

/**
* Get the property type mutability of this symbol if set.
*/
Expand All @@ -92,27 +110,30 @@ val Symbol.propertyTypeMutability: PropertyTypeMutability?

/**
* Gets the default value for the symbol if present, else null
* @param defaultBoxed the string to pass back for boxed values
* @param defaultNullable the string to pass back for nullable values
*/
fun Symbol.defaultValue(defaultBoxed: String? = "null"): String? {
// boxed types should always be defaulted to null
if (isBoxed) {
return defaultBoxed
}

fun Symbol.defaultValue(defaultNullable: String? = "null"): String? {
val default = getProperty(SymbolProperty.DEFAULT_VALUE_KEY, String::class.java)
return if (default.isPresent) default.get() else null

// nullable types should default to null if there is no modeled default
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably don't understand how this all fits, but why is isNullable not sufficient? Seems simpler if it ended up like that. What test case would break without the && check?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for the && check is if something's nullable but it has a default value modeled, we want to write that default.

With && check:

public val foo: String? = "my modeled default value"

Without && check:

public val foo: String? = null // (discards the modeled default value)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought if something has a non-null default value, it won't be nullable. Atleast when it uses CLIENT mode, per https://github.com/awslabs/smithy/blob/b69aeb5dae6639b4381575005722bea02bb31ca8/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/NullableIndex.java#L142.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, but they can be nullable because of @clientOptional or @input and still have default value. got it.

if (isNullable && (!default.isPresent || defaultValueType == DefaultValueType.INFERRED)) {
return defaultNullable
}
return default.getOrNull()
}

/**
* Mark a symbol as being boxed (nullable) i.e. `T?`
* Mark a symbol as being nullable (i.e. `T?`)
*/
fun Symbol.Builder.boxed(): Symbol.Builder = apply { putProperty(SymbolProperty.BOXED_KEY, true) }
fun Symbol.Builder.nullable(): Symbol.Builder = apply { putProperty(SymbolProperty.NULLABLE_KEY, true) }

/**
* Set the default value used when formatting the symbol
*/
fun Symbol.Builder.defaultValue(value: String): Symbol.Builder = apply { putProperty(SymbolProperty.DEFAULT_VALUE_KEY, value) }
fun Symbol.Builder.defaultValue(value: String?, type: DefaultValueType = DefaultValueType.INFERRED): Symbol.Builder = apply {
putProperty(SymbolProperty.DEFAULT_VALUE_KEY, value)
putProperty(SymbolProperty.DEFAULT_VALUE_TYPE_KEY, type)
}

/**
* Convenience function for specifying kotlin namespace
Expand Down Expand Up @@ -177,7 +198,7 @@ val Symbol.shape: Shape?
/**
* Get the nullable version of a symbol
*/
fun Symbol.asNullable(): Symbol = toBuilder().boxed().build()
fun Symbol.asNullable(): Symbol = toBuilder().nullable().build()

/**
* Check whether a symbol represents an extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class StructureGenerator(
// Return the appropriate hashCode fragment based on ShapeID of member target.
private fun selectHashFunctionForShape(member: MemberShape): String {
val targetShape = model.expectShape(member.target)
val isNullable = memberNameSymbolIndex[member]!!.second.isBoxed
val isNullable = memberNameSymbolIndex[member]!!.second.isNullable
return when (targetShape.type) {
ShapeType.INTEGER ->
when (isNullable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import software.amazon.smithy.kotlin.codegen.core.*
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
import software.amazon.smithy.kotlin.codegen.model.filterEventStreamErrors
import software.amazon.smithy.kotlin.codegen.model.hasTrait
import software.amazon.smithy.kotlin.codegen.model.isBoxed
import software.amazon.smithy.kotlin.codegen.model.isNullable
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.*
import software.amazon.smithy.model.traits.SensitiveTrait
Expand Down Expand Up @@ -126,12 +126,12 @@ class UnionGenerator(

return when (targetShape.type) {
ShapeType.INTEGER ->
when (targetSymbol.isBoxed) {
when (targetSymbol.isNullable) {
true -> " ?: 0"
else -> ""
}
ShapeType.BYTE ->
when (targetSymbol.isBoxed) {
when (targetSymbol.isNullable) {
true -> ".toInt() ?: 0"
else -> ".toInt()"
}
Expand All @@ -144,7 +144,7 @@ class UnionGenerator(
".contentHashCode()"
}
else ->
when (targetSymbol.isBoxed) {
when (targetSymbol.isNullable) {
true -> ".hashCode() ?: 0"
else -> ".hashCode()"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
val headerName = hdrBinding.locationName

val targetSymbol = ctx.symbolProvider.toSymbol(hdrBinding.member)
val defaultValuePostfix = if (targetSymbol.isNotBoxed && targetSymbol.defaultValue() != null) {
val defaultValuePostfix = if (targetSymbol.isNotNullable && targetSymbol.defaultValue() != null) {
" ?: ${targetSymbol.defaultValue()}"
} else {
""
Expand Down
Loading