feat(KT-48814): represent all of the external declarations nullable properties as an optional fields inside d.ts.

This commit is contained in:
Artem Kobzar
2022-09-02 10:50:20 +00:00
committed by Space
parent a52d8f0364
commit 0bb0be8703
12 changed files with 41 additions and 8 deletions
@@ -59,6 +59,7 @@ class ExportedProperty(
val isField: Boolean = false, val isField: Boolean = false,
val irGetter: IrFunction? = null, val irGetter: IrFunction? = null,
val irSetter: IrFunction? = null, val irSetter: IrFunction? = null,
val isOptional: Boolean = false
) : ExportedDeclaration() ) : ExportedDeclaration()
// TODO: Cover all cases with frontend and disable error declarations // TODO: Cover all cases with frontend and disable error declarations
@@ -139,6 +139,9 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
specializeType: ExportedType? = null specializeType: ExportedType? = null
): ExportedDeclaration { ): ExportedDeclaration {
val parentClass = property.parent as? IrClass val parentClass = property.parent as? IrClass
val isOptional = property.isEffectivelyExternal() &&
property.parent is IrClass &&
property.getter?.returnType?.isNullable() == true
return ExportedProperty( return ExportedProperty(
name = property.getExportedIdentifier(), name = property.getExportedIdentifier(),
@@ -149,7 +152,8 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
isProtected = property.visibility == DescriptorVisibilities.PROTECTED, isProtected = property.visibility == DescriptorVisibilities.PROTECTED,
isField = parentClass?.isInterface == true, isField = parentClass?.isInterface == true,
irGetter = property.getter, irGetter = property.getter,
irSetter = property.setter irSetter = property.setter,
isOptional = isOptional
) )
} }
@@ -133,7 +133,8 @@ class ExportModelToTsDeclarations {
"" ""
} else { } else {
val readonly = if (isMember && !mutable) "readonly " else "" val readonly = if (isMember && !mutable) "readonly " else ""
"$prefix$visibility$possibleStatic$keyword$readonly$memberName: $typeToTypeScript;" val optional = if (isOptional) "?" else ""
"$prefix$visibility$possibleStatic$keyword$readonly$memberName$optional: $typeToTypeScript;"
} }
} }
} }
@@ -25,7 +25,7 @@ object JsPlatformConfigurator : PlatformConfiguratorBase(
JsRuntimeAnnotationChecker, JsRuntimeAnnotationChecker,
JsDynamicDeclarationChecker, JsDynamicDeclarationChecker,
JsExportAnnotationChecker, JsExportAnnotationChecker,
JsExportDeclarationChecker JsExportDeclarationChecker,
), ),
additionalCallCheckers = listOf( additionalCallCheckers = listOf(
JsModuleCallChecker, JsModuleCallChecker,
@@ -2,8 +2,8 @@ declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
namespace foo { namespace foo {
interface I<T, S, U> { interface I<T, S, U> {
x: T; x?: T;
readonly y: S; readonly y?: S;
z(u: U): void; z(u: U): void;
} }
interface I2 { interface I2 {
@@ -1,5 +1,11 @@
declare namespace JS_TESTS { declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
namespace foo {
interface OptionalFieldsInterface {
readonly required: number;
readonly notRequired?: Nullable<number>;
}
}
namespace foo { namespace foo {
interface TestInterface { interface TestInterface {
readonly value: string; readonly value: string;
@@ -24,5 +30,6 @@ declare namespace JS_TESTS {
readonly __doNotUseOrImplementIt: foo.TestInterfaceImpl["__doNotUseOrImplementIt"] & foo.AnotherExportedInterface["__doNotUseOrImplementIt"]; readonly __doNotUseOrImplementIt: foo.TestInterfaceImpl["__doNotUseOrImplementIt"] & foo.AnotherExportedInterface["__doNotUseOrImplementIt"];
} }
function processInterface(test: foo.TestInterface): string; function processInterface(test: foo.TestInterface): string;
function processOptionalInterface(a: foo.OptionalFieldsInterface): string;
} }
} }
@@ -30,4 +30,15 @@ class ChildTestInterfaceImpl(): TestInterfaceImpl("Test"), AnotherExportedInterf
@JsExport @JsExport
fun processInterface(test: TestInterface): String { fun processInterface(test: TestInterface): String {
return "Owner ${test.getOwnerName()} has value '${test.value}'" return "Owner ${test.getOwnerName()} has value '${test.value}'"
}
@JsExport
external interface OptionalFieldsInterface {
val required: Int
val notRequired: Int?
}
@JsExport
fun processOptionalInterface(a: OptionalFieldsInterface): String {
return "${a.required}${a.notRequired ?: "unknown"}"
} }
@@ -2,6 +2,7 @@
var TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl; var TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl;
var ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl; var ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl;
var processInterface = JS_TESTS.foo.processInterface; var processInterface = JS_TESTS.foo.processInterface;
var processOptionalInterface = JS_TESTS.foo.processOptionalInterface;
function assert(condition) { function assert(condition) {
if (!condition) { if (!condition) {
throw "Assertion failed"; throw "Assertion failed";
@@ -12,5 +13,8 @@ function box() {
assert(processInterface(new ChildTestInterfaceImpl()) === "Owner TestInterfaceImpl has value 'Test'"); assert(processInterface(new ChildTestInterfaceImpl()) === "Owner TestInterfaceImpl has value 'Test'");
// @ts-expect-error "Just test that this code will throw compilation error for a user" // @ts-expect-error "Just test that this code will throw compilation error for a user"
assert(processInterface({ value: "bar", getOwnerName: function () { return "RandomObject"; } }) === "Owner RandomObject has value 'bar'"); assert(processInterface({ value: "bar", getOwnerName: function () { return "RandomObject"; } }) === "Owner RandomObject has value 'bar'");
assert(processOptionalInterface({ required: 4 }) == "4unknown");
assert(processOptionalInterface({ required: 4, notRequired: null }) == "4unknown");
assert(processOptionalInterface({ required: 4, notRequired: 5 }) == "45");
return "OK"; return "OK";
} }
@@ -1,6 +1,7 @@
import TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl; import TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl;
import ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl; import ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl;
import processInterface = JS_TESTS.foo.processInterface; import processInterface = JS_TESTS.foo.processInterface;
import processOptionalInterface = JS_TESTS.foo.processOptionalInterface;
function assert(condition: boolean) { function assert(condition: boolean) {
if (!condition) { if (!condition) {
@@ -15,5 +16,9 @@ function box(): string {
// @ts-expect-error "Just test that this code will throw compilation error for a user" // @ts-expect-error "Just test that this code will throw compilation error for a user"
assert(processInterface({ value: "bar", getOwnerName: () => "RandomObject" }) === "Owner RandomObject has value 'bar'") assert(processInterface({ value: "bar", getOwnerName: () => "RandomObject" }) === "Owner RandomObject has value 'bar'")
assert(processOptionalInterface({ required: 4 }) == "4unknown")
assert(processOptionalInterface({ required: 4, notRequired: null }) == "4unknown")
assert(processOptionalInterface({ required: 4, notRequired: 5 }) == "45")
return "OK"; return "OK";
} }
@@ -2,7 +2,7 @@ declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
namespace foo { namespace foo {
interface Object { interface Object {
readonly constructor: any; readonly constructor?: any;
} }
} }
namespace foo { namespace foo {
@@ -2,7 +2,7 @@ declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
namespace foo { namespace foo {
interface Object { interface Object {
readonly constructor: any; readonly constructor?: any;
} }
} }
namespace foo { namespace foo {
@@ -200,4 +200,4 @@ public actual annotation class JsExport
@Target(AnnotationTarget.PROPERTY) @Target(AnnotationTarget.PROPERTY)
@SinceKotlin("1.6") @SinceKotlin("1.6")
@Deprecated("This annotation is a temporal migration assistance and may be removed in the future releases, please consider filing an issue about the case where it is needed") @Deprecated("This annotation is a temporal migration assistance and may be removed in the future releases, please consider filing an issue about the case where it is needed")
public annotation class EagerInitialization public annotation class EagerInitialization