All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.js.resolve.diagnostics.JsExportDeclarationChecker.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.js.resolve.diagnostics

import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.ClassKind.*
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty
import org.jetbrains.kotlin.resolve.inline.isInlineWithReified
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.isDynamic
import org.jetbrains.kotlin.types.typeUtil.*

object JsExportDeclarationChecker : DeclarationChecker {
    override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
        val trace = context.trace
        val bindingContext = trace.bindingContext

        fun checkTypeParameter(descriptor: TypeParameterDescriptor) {
            for (upperBound in descriptor.upperBounds) {
                if (!upperBound.isExportable(bindingContext)) {
                    val typeParameterDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor)!!
                    trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(typeParameterDeclaration, "upper bound", upperBound))
                }
            }
        }

        fun checkValueParameter(descriptor: ValueParameterDescriptor) {
            if (!descriptor.type.isExportable(bindingContext)) {
                val valueParameterDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor)!!
                trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(valueParameterDeclaration, "parameter", descriptor.type))
            }
        }

        if (!AnnotationsUtils.isExportedObject(descriptor, bindingContext)) return
        if (descriptor !is MemberDescriptor)
            return

        val hasJsName = AnnotationsUtils.getJsNameAnnotation(descriptor) != null

        fun reportWrongExportedDeclaration(kind: String) {
            trace.report(ErrorsJs.WRONG_EXPORTED_DECLARATION.on(declaration, kind))
        }

        if (descriptor.isExpect) {
            reportWrongExportedDeclaration("expect")
        }

        when (descriptor) {
            is FunctionDescriptor -> {
                for (typeParameter in descriptor.typeParameters) {
                    checkTypeParameter(typeParameter)
                }

                if (descriptor.isInlineWithReified()) {
                    reportWrongExportedDeclaration("inline function with reified type parameters")
                    return
                }

                if (descriptor.isSuspend) {
                    reportWrongExportedDeclaration("suspend function")
                    return
                }

                if (descriptor is ConstructorDescriptor) {
                    if (!descriptor.isPrimary && !hasJsName)
                        reportWrongExportedDeclaration("secondary constructor without @JsName")
                }

                // Properties are checked instead of property accessors
                if (descriptor !is PropertyAccessorDescriptor) {
                    for (parameter in descriptor.valueParameters) {
                        checkValueParameter(parameter)
                    }

                    descriptor.returnType?.let { returnType ->
                        if (!returnType.isExportableReturn(bindingContext)) {
                            trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(declaration, "return", returnType))
                        }
                    }
                }
            }

            is PropertyDescriptor -> {
                if (descriptor.isExtensionProperty) {
                    reportWrongExportedDeclaration("extension property")
                    return
                }
                if (!descriptor.type.isExportable(bindingContext)) {
                    trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(declaration, "property", descriptor.type))
                }
            }

            is ClassDescriptor -> {
                for (typeParameter in descriptor.declaredTypeParameters) {
                    checkTypeParameter(typeParameter)
                }

                if (descriptor.kind == ENUM_CLASS) {
                    reportWrongExportedDeclaration("enum class")
                    return
                }
                if (descriptor.kind == ANNOTATION_CLASS) {
                    reportWrongExportedDeclaration("annotation class")
                    return
                }
                if (descriptor.kind == ENUM_ENTRY) {
                    // Covered by ENUM_CLASS
                    return
                }

                for (superType in descriptor.defaultType.supertypes()) {
                    if (!superType.isExportable(bindingContext)) {
                        trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(declaration, "super", superType))
                    }
                }
            }
        }
    }

    private fun KotlinType.isExportableReturn(bindingContext: BindingContext, currentlyProcessed: MutableSet = mutableSetOf()) =
        isUnit() || isExportable(bindingContext, currentlyProcessed)

    private fun KotlinType.isExportable(
        bindingContext: BindingContext,
        currentlyProcessed: MutableSet = mutableSetOf()
    ): Boolean {
        if (!currentlyProcessed.add(this)) {
            return true
        }

        currentlyProcessed.add(this)

        if (isFunctionType) {
            for (i in 0 until arguments.lastIndex) {
                if (!arguments[i].type.isExportable(bindingContext, currentlyProcessed)) {
                    currentlyProcessed.remove(this)
                    return false
                }
            }

            currentlyProcessed.remove(this)
            return arguments.last().type.isExportableReturn(bindingContext, currentlyProcessed)
        }

        for (argument in arguments) {
            if (!argument.type.isExportable(bindingContext, currentlyProcessed)) {
                currentlyProcessed.remove(this)
                return false
            }
        }

        currentlyProcessed.remove(this)

        val nonNullable = makeNotNullable()

        val isPrimitiveExportableType = nonNullable.isAnyOrNullableAny() ||
                nonNullable.isDynamic() ||
                nonNullable.isBoolean() ||
                KotlinBuiltIns.isThrowableOrNullableThrowable(nonNullable) ||
                KotlinBuiltIns.isString(nonNullable) ||
                (nonNullable.isPrimitiveNumberOrNullableType() && !nonNullable.isLong()) ||
                nonNullable.isNothingOrNullableNothing() ||
                KotlinBuiltIns.isArray(this) ||
                KotlinBuiltIns.isPrimitiveArray(this)

        if (isPrimitiveExportableType) return true

        val descriptor = constructor.declarationDescriptor

        if (descriptor !is MemberDescriptor) return false

        return descriptor.isEffectivelyExternal() || AnnotationsUtils.isExportedObject(descriptor, bindingContext)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy