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

org.jetbrains.kotlin.fir.analysis.checkers.expression.FirUpperBoundViolatedChecker.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2020 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.fir.analysis.checkers.expression

import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.originalForSubstitutionOverride
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.AbstractTypeCheckerContext
import org.jetbrains.kotlin.utils.addToStdlib.min
import org.jetbrains.kotlin.utils.addToStdlib.safeAs

object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() {
    override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
        // something that contains the type parameters
        // declarations with their declared bounds.
        // it may be the called function declaration
        // or the class declaration
        val calleeFir = expression.calleeReference.safeAs()
            ?.resolvedSymbol
            ?.fir.safeAs()
            ?: return

        val count = min(calleeFir.typeParameters.size, expression.typeArguments.size)

        if (count == 0) {
            return
        }

        val parameterPairs = mutableMapOf()

        for (it in 0 until count) {
            expression.typeArguments[it].safeAs()
                ?.typeRef.safeAs()
                ?.let { that ->
                    if (that !is FirErrorTypeRef) {
                        parameterPairs[calleeFir.typeParameters[it].symbol] = that
                    } else {
                        return
                    }
                }
        }

        // we substitute actual values to the
        // type parameters from the declaration
        val substitutor = substitutorByMap(
            parameterPairs.mapValues { it.value.type }
        )

        val typeCheckerContext = context.session.typeContext.newBaseTypeCheckerContext(
            errorTypesEqualToAnything = false,
            stubTypesEqualToAnything = false
        )

        parameterPairs.forEach { (proto, actual) ->
            if (actual.source == null) {
                // inferred types don't report INAPPLICABLE_CANDIDATE for type aliases!
                return@forEach
            }

            if (!satisfiesBounds(proto, actual.type, substitutor, typeCheckerContext)) {
                reporter.report(actual.source, proto, actual.type)
                return
            }

            // we must analyze nested things like
            // S, T>()
            actual.type.safeAs()?.let {
                val errorOccurred = analyzeTypeParameters(it, context, reporter, typeCheckerContext, actual.source)

                if (errorOccurred) {
                    return
                }
            }
        }

        // if we're dealing with a constructor
        // resolved from a typealias we need to
        // check if our actual parameters satisfy
        // this constructor parameters.
        // e.g.
        // class B>
        // typealias A = B>
        // val a = A()
        when (calleeFir) {
            is FirConstructor -> analyzeConstructorCall(expression, substitutor, typeCheckerContext, reporter)
        }
    }

    private fun analyzeConstructorCall(
        functionCall: FirQualifiedAccessExpression,
        callSiteSubstitutor: ConeSubstitutor,
        typeCheckerContext: AbstractTypeCheckerContext,
        reporter: DiagnosticReporter
    ) {
        // holds Collection bound.
        // note that if B used another type parameter here,
        // we'd get Collection. So we need to do one more
        // substitution here
        val protoConstructor = functionCall.calleeReference.safeAs()
            ?.resolvedSymbol.safeAs()
            ?.fir?.originalForSubstitutionOverride
            ?: return

        // holds Collection bound.
        // we need to do substitution here to get
        // Collection
        val actualConstructor = functionCall.calleeReference.safeAs()
            ?.resolvedSymbol.safeAs()
            ?.fir.safeAs()
            ?.returnTypeRef.safeAs()
            ?.type.safeAs()
            ?: return

        val count = min(protoConstructor.typeParameters.size, actualConstructor.typeArguments.size)

        if (count == 0) {
            return
        }

        val constructorsParameterPairs = mutableMapOf()

        for (it in 0 until count) {
            actualConstructor.typeArguments[it].safeAs()
                ?.let { that ->
                    if (that !is ConeClassErrorType) {
                        constructorsParameterPairs[protoConstructor.typeParameters[it].symbol] = that
                    } else {
                        return
                    }
                }
        }

        // we substitute typealias declaration
        // parameters to the ones used in the
        // typealias target
        val declarationSiteSubstitutor = substitutorByMap(
            constructorsParameterPairs.toMap().mapValues { it.value.type }
        )

        constructorsParameterPairs.forEach { (proto, actual) ->
            // just in case
            var intersection = typeCheckerContext.intersectTypes(
                proto.fir.bounds.filterIsInstance().map { it.type }
            ).safeAs() ?: return@forEach

            intersection = declarationSiteSubstitutor.substituteOrSelf(intersection)
            intersection = callSiteSubstitutor.substituteOrSelf(intersection)

            // substitute Int for G from
            // the example above
            val target = callSiteSubstitutor.substituteOrSelf(actual)
            val satisfiesBounds = AbstractTypeChecker.isSubtypeOf(typeCheckerContext, target, intersection)

            if (!satisfiesBounds) {
                reporter.report(functionCall.source, proto, actual)
                return
            }
        }
    }

    /**
     * Recursively analyzes type parameters
     * and reports the diagnostic on the given
     * reportTarget (because we can't report them
     * on type parameters themselves now).
     * Returns true if an error occured
     */
    private fun analyzeTypeParameters(
        type: ConeClassLikeType,
        context: CheckerContext,
        reporter: DiagnosticReporter,
        typeCheckerContext: AbstractTypeCheckerContext,
        reportTarget: FirSourceElement?
    ): Boolean {
        val prototypeClass = type.lookupTag.toSymbol(context.session)
            ?.fir.safeAs()
            ?: return false

        val count = min(prototypeClass.typeParameters.size, type.typeArguments.size)

        if (count == 0) {
            return false
        }

        val parameterPairs = mutableMapOf()

        for (it in 0 until count) {
            type.typeArguments[it].safeAs()
                ?.let { that ->
                    if (that !is ConeClassErrorType) {
                        parameterPairs[prototypeClass.typeParameters[it].symbol] = that
                    } else {
                        return true
                    }
                }
        }

        val substitutor = substitutorByMap(
            parameterPairs.toMap().mapValues { it.value.type }
        )

        parameterPairs.forEach { (proto, actual) ->
            if (!satisfiesBounds(proto, actual.type, substitutor, typeCheckerContext)) {
                // should report on the parameter instead!
                reporter.report(reportTarget, proto, actual)
                return true
            }

            val errorOccurred = analyzeTypeParameters(actual, context, reporter, typeCheckerContext, reportTarget)

            if (errorOccurred) {
                return true
            }
        }

        return false
    }

    /**
     * Returns true if target satisfies the
     * bounds of the prototypeSymbol.
     */
    private fun satisfiesBounds(
        prototypeSymbol: FirTypeParameterSymbol,
        target: ConeKotlinType,
        substitutor: ConeSubstitutor,
        typeCheckerContext: AbstractTypeCheckerContext
    ): Boolean {
        var intersection = typeCheckerContext.intersectTypes(
            prototypeSymbol.fir.bounds.filterIsInstance().map { it.type }
        ).safeAs() ?: return true

        intersection = substitutor.substituteOrSelf(intersection)
        return AbstractTypeChecker.isSubtypeOf(typeCheckerContext, target, intersection)
    }

    private fun DiagnosticReporter.report(source: FirSourceElement?, proto: FirTypeParameterSymbol, actual: ConeKotlinType) {
        source?.let {
            report(FirErrors.UPPER_BOUND_VIOLATED.on(it, proto, actual))
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy