override fun annotate()

in gdscript/src/main/kotlin/gdscript/annotator/GdParamAnnotator.kt [31:196]


    override fun annotate(element: PsiElement, holder: AnnotationHolder) {
        if (element !is GdCallEx) return

        var minSize = 99
        var maxSize = 0

        val refId = PsiTreeUtil.findChildrenOfType(element.expr, GdRefIdRef::class.java).lastOrNull() ?: return
        val ref = refId.references.firstOrNull() ?: return
        if (ref !is GdClassMemberReference) return
        val declaration = ref.resolveDeclaration() ?: return
        val descriptions = mutableListOf<String>()

        val paramLists = when (declaration) {
            is GdMethodDeclTl -> {
                if (declaration.isVariadic) return
                if (declaration.name == "emit") {
                    val signal = PsiGdSignalUtil.getDeclaration(element) ?: return
                    descriptions.add(declaration.shortMethodHeader())
                    arrayOf(signal.paramList?.paramList)
                } else {
                    descriptions.add(declaration.shortMethodHeader())
                    arrayOf(declaration.paramList?.paramList)
                }
            }

            is GdVarDeclSt -> {
                val lambda =
                    if (declaration.expr is GdFuncDeclEx) declaration.expr as GdFuncDeclEx else null ?: return
                descriptions.add(lambda.shortMethodHeader())
                arrayOf(lambda.paramList?.paramList)
            }

            is GdClassNaming -> {
                GdClassMemberUtil
                    .listClassMemberDeclarations(declaration, constructors = true)
                    .constructors()
                    .map {
                        descriptions.add(it.shortMethodHeader())
                        it.paramList?.paramList
                    }
                    .toTypedArray()
            }

            else -> null
        }?.mapNotNull { it } ?: return

        if (descriptions.size > paramLists.size) minSize = 0 // filtered by mapNotNull of empty constructor

        val paramTypes: HashMap<Int, MutableList<String>> = hashMapOf()
        paramLists.forEachIndexed { index, params ->
            minSize = minOf(minSize, params.size)
            maxSize = maxOf(maxSize, params.size)
            for (i in 0 until params.size) {
                if (params[i].expr != null) {
                    minSize = minOf(minSize, i)
                    break
                }
            }

            paramTypes[index] = mutableListOf()
            params.forEach { param ->
                paramTypes[index]!!.add(param.returnType)
            }
        }

        val usedParamSize = element.argList?.argExprList?.size ?: 0

        // Check number of arguments
        if (usedParamSize > maxSize && element.argList != null) {
            val toRemoveList = mutableListOf<PsiElement>()
            var toRemove: PsiElement? = element.argList!!.argExprList[maxSize]
            if (maxSize > 0) toRemoveList.add(toRemove!!.prevNonWhiteCommentToken()!!)

            while (toRemove != null) {
                toRemoveList.add(toRemove)
                toRemove = toRemove.nextNonWhiteCommentToken()
            }

            holder
                .newAnnotationGd(element.project, HighlightSeverity.ERROR, GdScriptBundle.message("annotator.too.many.arguments"))
                .range(element.textRange)
                .withFix(GdRemoveElementsAction(*toRemoveList.toTypedArray()))
                .create()
            return
        } else if (minSize in 1..98 && usedParamSize < minSize) {
            holder
                .newAnnotationGd(element.project, HighlightSeverity.ERROR, GdScriptBundle.message("annotator.not.enough.arguments"))
                .range(element.textRange)
                .create()
            return
        }

        if (usedParamSize == 0) return

        // Check arguments types
        val actualTypes = element.argList?.argExprList?.map { it.returnType }?.toTypedArray() ?: emptyArray()

        val matched = paramTypes.values
            .filter { it.size == usedParamSize }
            .map { definedParams ->
                definedParams.mapIndexed { pIndex, definedParam ->
                    GdExprUtil.typeAccepts(actualTypes[pIndex], definedParam, element)
                }
            }

        if (matched.isEmpty()) return

        // One of overrides matched all params
        if (matched.any { it.all { p -> p } }) return


        if (paramLists.size > 1) {
            val tooltip = HtmlBuilder()
                .append(GdScriptBundle.message("annotator.no.overload.matches"))
                .br()
                .append(
                    HtmlChunk.ul().children(
                        descriptions.map { HtmlChunk.li().child(HtmlChunk.text(it).bold()) }
                    ))
                .wrapWithHtmlBody()
                .toString()

            holder
                .newAnnotationGd(element.project, HighlightSeverity.ERROR, "")
                .tooltip(tooltip)
                .range(element.textRange)
                .create()
            return
        } else {
            val params = paramLists.first()
            matched.first().forEachIndexed { pIndex, ok ->
                if (!ok) {
                    val param = params[pIndex] ?: return@forEachIndexed
                    val actualParam = element.argList?.argExprList?.getOrNull(pIndex) ?: return@forEachIndexed
                    val actualType = actualTypes[pIndex]

                    val tooltip = HtmlBuilder()
                        .append(GdScriptBundle.message("annotator.type.mismatch.for.parameter", param.varNmi.name)).br()
                        .append(
                            HtmlChunk.tag("table").children(
                                HtmlChunk.tag("tr").children(
                                    HtmlChunk.tag("td").addText(GdScriptBundle.message("annotator.required")),
                                    HtmlChunk.tag("td").addText(param.returnType)
                                ),
                                HtmlChunk.tag("tr").children(
                                    HtmlChunk.tag("td").addText(GdScriptBundle.message("annotator.found")),
                                    HtmlChunk.tag("td").addText(actualType)
                                )
                            )
                        )
                        .wrapWithHtmlBody()
                        .toString()

                    val annotator = holder
                        .newAnnotationGd(element.project, HighlightSeverity.ERROR, "")
                        .tooltip(tooltip)
                        .range(actualParam.textRange)
                    if (!actualType.isDynamicType() && param.typed != null) {
                        annotator.withFix(GdChangeTypeFix(param.typed!!.typedVal, actualType))
                    }
                    annotator.create()
                }
            }
            return
        }
    }