From 13d87f80206f789d10742586a9b47eb5697feb81 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 31 Jan 2025 20:55:38 +0100 Subject: [PATCH] Fixed all tests --- .../cpg/passes/EvaluationOrderGraphPass.kt | 5 +- .../frontends/python/DeclarationHandler.kt | 331 ++++++++++++++++++ .../cpg/frontends/python/ExpressionHandler.kt | 2 +- .../python/PythonLanguageFrontend.kt | 9 +- .../cpg/frontends/python/StatementHandler.kt | 274 +-------------- 5 files changed, 350 insertions(+), 271 deletions(-) create mode 100644 cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/DeclarationHandler.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 7dd54fd62b3..217d2206099 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -344,7 +344,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa * e.g. [LoopStatement]s or [BreakStatement]. */ protected fun handleEOG(node: Node?) { - if (node == null || alreadySeen.contains(node)) { + if (node == null) { return } @@ -543,8 +543,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa } else if (declaration is FunctionDeclaration) { // save the current EOG stack, because we can have a function declaration within an // existing function and the EOG handler for handling function declarations will - // reset the - // stack + // reset the stack val oldEOG = currentPredecessors.toMutableList() // analyze the defaults diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/DeclarationHandler.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/DeclarationHandler.kt new file mode 100644 index 00000000000..d65d100a593 --- /dev/null +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/DeclarationHandler.kt @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2025, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.frontends.python + +import de.fraunhofer.aisec.cpg.frontends.HasOperatorOverloading +import de.fraunhofer.aisec.cpg.frontends.isKnownOperatorName +import de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage.Companion.MODIFIER_KEYWORD_ONLY_ARGUMENT +import de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage.Companion.MODIFIER_POSITIONAL_ONLY_ARGUMENT +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.* +import de.fraunhofer.aisec.cpg.graph.scopes.FunctionScope +import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.types.FunctionType +import de.fraunhofer.aisec.cpg.helpers.Util + +/** + * In Python, all declarations are statements. This class handles the parsing of the statements + * represented as [Declaration] nodes in our CPG. + * + * For declarations encountered directly on a namespace and classes, we directly invoke the + * [DeclarationHandler], for others, the [StatementHandler] will forward these statements to us. + */ +class DeclarationHandler(frontend: PythonLanguageFrontend) : + PythonHandler(::ProblemDeclaration, frontend) { + override fun handleNode(node: Python.AST.BaseStmt): Declaration { + return when (node) { + is Python.AST.FunctionDef -> handleFunctionDef(node) + is Python.AST.AsyncFunctionDef -> handleFunctionDef(node) + else -> { + return handleNotSupported(node, node.javaClass.simpleName) + } + } + } + + private fun handleNotSupported(node: Python.AST.BaseStmt, name: String): Declaration { + Util.errorWithFileLocation( + frontend, + node, + log, + "Parsing of type $name is not supported (yet)", + ) + + val cpgNode = this.configConstructor.get() + if (cpgNode is ProblemNode) { + cpgNode.problem = "Parsing of type $name is not supported (yet)" + } + + return cpgNode + } + + /** + * We have to consider multiple things when matching Python's FunctionDef to the CPG: + * - A [Python.AST.FunctionDef] could be one of + * - a [ConstructorDeclaration] if it appears in a record and its [name] is `__init__` + * - a [MethodeDeclaration] if it appears in a record, and it isn't a + * [ConstructorDeclaration] + * - a [FunctionDeclaration] if neither of the above apply + * + * In case of a [ConstructorDeclaration] or[MethodDeclaration]: the first argument is the + * `receiver` (most often called `self`). + */ + private fun handleFunctionDef(s: Python.AST.NormalOrAsyncFunctionDef): FunctionDeclaration { + var recordDeclaration = + (frontend.scopeManager.currentScope as? RecordScope)?.astNode as? RecordDeclaration + val language = language + val result = + if (recordDeclaration != null) { + if (s.name == "__init__") { + newConstructorDeclaration( + name = s.name, + recordDeclaration = recordDeclaration, + rawNode = s, + ) + } else if (language is HasOperatorOverloading && s.name.isKnownOperatorName) { + var decl = + newOperatorDeclaration( + name = s.name, + recordDeclaration = recordDeclaration, + operatorCode = language.operatorCodeFor(s.name) ?: "", + rawNode = s, + ) + if (decl.operatorCode == "") { + Util.warnWithFileLocation( + decl, + log, + "Could not find operator code for operator {}. This will most likely result in a failure", + s.name, + ) + } + decl + } else { + newMethodDeclaration( + name = s.name, + recordDeclaration = recordDeclaration, + isStatic = false, + rawNode = s, + ) + } + } else { + newFunctionDeclaration(name = s.name, rawNode = s) + } + frontend.scopeManager.enterScope(result) + + frontend.statementHandler.addAsyncWarning(s, result) + + // Handle decorators (which are translated into CPG "annotations") + result.annotations += frontend.statementHandler.handleAnnotations(s) + + // Handle return type and calculate function type + if (result is ConstructorDeclaration) { + // Return type of the constructor is always its record declaration type + result.returnTypes = listOf(recordDeclaration?.toType() ?: unknownType()) + } else { + result.returnTypes = listOf(frontend.typeOf(s.returns)) + } + result.type = FunctionType.computeType(result) + + handleArguments(s.args, result, recordDeclaration) + + if (s.body.isNotEmpty()) { + result.body = frontend.statementHandler.makeBlock(s.body, parentNode = s) + } + + frontend.scopeManager.leaveScope(result) + + // We want functions inside functions to be wrapped in a declaration statement, so we are + // not adding them to scope's AST + if (frontend.scopeManager.currentScope is FunctionScope) { + frontend.scopeManager.addDeclaration(result, addToAST = false) + } else { + // In any other cases, we add them to the "declarations", otherwise we will not + // correctly resolve them. + frontend.scopeManager.addDeclaration(result) + } + + return result + } + + /** Adds the arguments to [result] which might be located in a [recordDeclaration]. */ + private fun handleArguments( + args: Python.AST.arguments, + result: FunctionDeclaration, + recordDeclaration: RecordDeclaration?, + ) { + // We can merge posonlyargs and args because both are positional arguments. We do not + // enforce that posonlyargs can ONLY be used in a positional style, whereas args can be used + // both in positional and keyword style. + var positionalArguments = args.posonlyargs + args.args + + // Handle receiver if it exists and if it is not a static method + if ( + recordDeclaration != null && + result.annotations.none { it.name.localName == "staticmethod" } + ) { + handleReceiverArgument(positionalArguments, args, result, recordDeclaration) + // Skip the receiver argument for further processing + positionalArguments = positionalArguments.drop(1) + } + + // Handle remaining arguments + handlePositionalArguments(positionalArguments, args) + + args.vararg?.let { handleArgument(it, isPosOnly = false, isVariadic = true) } + args.kwarg?.let { handleArgument(it, isPosOnly = false, isVariadic = false) } + + handleKeywordOnlyArguments(args) + } + + /** + * This function creates a [newParameterDeclaration] for the argument, setting any modifiers + * (like positional-only or keyword-only) and [defaultValue] if applicable. + */ + internal fun handleArgument( + node: Python.AST.arg, + isPosOnly: Boolean = false, + isVariadic: Boolean = false, + isKwoOnly: Boolean = false, + defaultValue: Expression? = null, + ): ParameterDeclaration { + val type = frontend.typeOf(node.annotation) + val arg = + newParameterDeclaration( + name = node.arg, + type = type, + variadic = isVariadic, + rawNode = node, + ) + defaultValue?.let { arg.default = it } + if (isPosOnly) { + arg.modifiers += MODIFIER_POSITIONAL_ONLY_ARGUMENT + } + + if (isKwoOnly) { + arg.modifiers += MODIFIER_KEYWORD_ONLY_ARGUMENT + } + + frontend.scopeManager.addDeclaration(arg) + + return arg + } + + /** + * This method retrieves the first argument of the [positionalArguments], which is typically the + * receiver object. + * + * A receiver can also have a default value. However, this case is not handled and is therefore + * passed with a problem expression. + */ + private fun handleReceiverArgument( + positionalArguments: List, + args: Python.AST.arguments, + result: FunctionDeclaration, + recordDeclaration: RecordDeclaration, + ) { + // first argument is the receiver + val recvPythonNode = positionalArguments.firstOrNull() + if (recvPythonNode == null) { + result.additionalProblems += newProblemExpression("Expected a receiver", rawNode = args) + } else { + val tpe = recordDeclaration.toType() + val recvNode = + newVariableDeclaration( + name = recvPythonNode.arg, + type = tpe, + implicitInitializerAllowed = false, + rawNode = recvPythonNode, + ) + + // If the number of defaults equals the number of positional arguments, the receiver has + // a default value + if (args.defaults.size == positionalArguments.size) { + val defaultValue = + args.defaults.getOrNull(0)?.let { frontend.expressionHandler.handle(it) } + defaultValue?.let { + frontend.scopeManager.addDeclaration(recvNode) + result.additionalProblems += + newProblemExpression("Receiver with default value", rawNode = args) + } + } + + when (result) { + is ConstructorDeclaration, + is MethodDeclaration -> result.receiver = recvNode + else -> + result.additionalProblems += + newProblemExpression( + problem = + "Expected a constructor or method declaration. Got something else.", + rawNode = result, + ) + } + } + } + + /** + * This method extracts the [positionalArguments] including those that may have default values. + * + * In Python only the arguments with default values are stored in `args.defaults`. + * https://docs.python.org/3/library/ast.html#ast.arguments + * + * For example: `def my_func(a, b=1, c=2): pass` + * + * In this case, `args.defaults` contains only the defaults for `b` and `c`, while `args.args` + * includes all arguments (`a`, `b` and `c`). The number of arguments without defaults is + * determined by subtracting the size of `args.defaults` from the total number of arguments. + * This matches each default to its corresponding argument. + * + * From the Python docs: "If there are fewer defaults, they correspond to the last n arguments." + */ + private fun handlePositionalArguments( + positionalArguments: List, + args: Python.AST.arguments, + ) { + val nonDefaultArgsCount = positionalArguments.size - args.defaults.size + + for (idx in positionalArguments.indices) { + val arg = positionalArguments[idx] + val defaultIndex = idx - nonDefaultArgsCount + val defaultValue = + if (defaultIndex >= 0) { + args.defaults.getOrNull(defaultIndex)?.let { + frontend.expressionHandler.handle(it) + } + } else { + null + } + handleArgument(arg, isPosOnly = arg in args.posonlyargs, defaultValue = defaultValue) + } + } + + /** + * This method extracts the keyword-only arguments from [args] and maps them to the + * corresponding function parameters. + */ + private fun handleKeywordOnlyArguments(args: Python.AST.arguments) { + for (idx in args.kwonlyargs.indices) { + val arg = args.kwonlyargs[idx] + val default = args.kw_defaults.getOrNull(idx) + handleArgument( + arg, + isPosOnly = false, + isKwoOnly = true, + defaultValue = default?.let { frontend.expressionHandler.handle(it) }, + ) + } + } +} diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt index 3da8338b34b..d90de20aea2 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt @@ -568,7 +568,7 @@ class ExpressionHandler(frontend: PythonLanguageFrontend) : val function = newFunctionDeclaration(name = "", rawNode = node) frontend.scopeManager.enterScope(function) for (arg in node.args.args) { - this.frontend.statementHandler.handleArgument(arg) + this.frontend.declarationHandler.handleArgument(arg) } function.body = handle(node.body) frontend.scopeManager.leaveScope(function) diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt index 9046509a31a..961c66d42f9 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt @@ -66,8 +66,7 @@ class PythonLanguageFrontend(language: Language, ctx: Tr private val tokenTypeIndex = 0 private val jep = JepSingleton // configure Jep - // val declarationHandler = DeclarationHandler(this) - // val specificationHandler = SpecificationHandler(this) + internal val declarationHandler = DeclarationHandler(this) internal var statementHandler = StatementHandler(this) internal var expressionHandler = ExpressionHandler(this) @@ -313,7 +312,11 @@ class PythonLanguageFrontend(language: Language, ctx: Tr if (lastNamespace != null) { for (stmt in pythonASTModule.body) { - lastNamespace.statements += statementHandler.handle(stmt) + if (stmt is Python.AST.FunctionDef || stmt is Python.AST.AsyncFunctionDef) { + declarationHandler.handleNode(stmt) + } else { + lastNamespace.statements += statementHandler.handle(stmt) + } } } diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt index 292748d6bca..90c50419c1e 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt @@ -25,11 +25,7 @@ */ package de.fraunhofer.aisec.cpg.frontends.python -import de.fraunhofer.aisec.cpg.frontends.HasOperatorOverloading -import de.fraunhofer.aisec.cpg.frontends.isKnownOperatorName import de.fraunhofer.aisec.cpg.frontends.python.Python.AST.IsAsync -import de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage.Companion.MODIFIER_KEYWORD_ONLY_ARGUMENT -import de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage.Companion.MODIFIER_POSITIONAL_ONLY_ARGUMENT import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.Annotation import de.fraunhofer.aisec.cpg.graph.declarations.* @@ -44,7 +40,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.statements.TryStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.helpers.Util import kotlin.collections.plusAssign @@ -54,8 +49,6 @@ class StatementHandler(frontend: PythonLanguageFrontend) : override fun handleNode(node: Python.AST.BaseStmt): Statement { return when (node) { is Python.AST.ClassDef -> handleClassDef(node) - is Python.AST.FunctionDef -> handleFunctionDef(node) - is Python.AST.AsyncFunctionDef -> handleFunctionDef(node) is Python.AST.Pass -> return newEmptyStatement(rawNode = node) is Python.AST.ImportFrom -> handleImportFrom(node) is Python.AST.Assign -> handleAssign(node) @@ -84,6 +77,10 @@ class StatementHandler(frontend: PythonLanguageFrontend) : problem = "The statement of class ${node.javaClass} is not supported yet", rawNode = node, ) + is Python.AST.AsyncFunctionDef -> + wrapDeclarationToStatement(frontend.declarationHandler.handleNode(node)) + is Python.AST.FunctionDef -> + wrapDeclarationToStatement(frontend.declarationHandler.handleNode(node)) } } @@ -807,11 +804,9 @@ class StatementHandler(frontend: PythonLanguageFrontend) : for (s in stmt.body) { when (s) { - is Python.AST.FunctionDef -> { - val stmt = handleFunctionDef(s, cls) - // We need to manually set the astParent because we are not assigning it to our - // statements and therefore are not triggering our automagic parent setter - stmt.astParent = cls + is Python.AST.FunctionDef, + is Python.AST.AsyncFunctionDef -> { + frontend.declarationHandler.handleNode(s) } else -> cls.statements += handleNode(s) } @@ -823,88 +818,6 @@ class StatementHandler(frontend: PythonLanguageFrontend) : return wrapDeclarationToStatement(cls) } - /** - * We have to consider multiple things when matching Python's FunctionDef to the CPG: - * - A [Python.AST.FunctionDef] is a [Statement] from Python's point of view. The CPG sees it as - * a declaration -> we have to wrap the result in a [DeclarationStatement]. - * - A [Python.AST.FunctionDef] could be one of - * - a [ConstructorDeclaration] if it appears in a record and its [name] is `__init__` - * - a [MethodeDeclaration] if it appears in a record, and it isn't a - * [ConstructorDeclaration] - * - a [FunctionDeclaration] if neither of the above apply - * - * In case of a [ConstructorDeclaration] or[MethodDeclaration]: the first argument is the - * `receiver` (most often called `self`). - */ - private fun handleFunctionDef( - s: Python.AST.NormalOrAsyncFunctionDef, - recordDeclaration: RecordDeclaration? = null, - ): DeclarationStatement { - val language = language - val result = - if (recordDeclaration != null) { - if (s.name == "__init__") { - newConstructorDeclaration( - name = s.name, - recordDeclaration = recordDeclaration, - rawNode = s, - ) - } else if (language is HasOperatorOverloading && s.name.isKnownOperatorName) { - var decl = - newOperatorDeclaration( - name = s.name, - recordDeclaration = recordDeclaration, - operatorCode = language.operatorCodeFor(s.name) ?: "", - rawNode = s, - ) - if (decl.operatorCode == "") { - Util.warnWithFileLocation( - decl, - log, - "Could not find operator code for operator {}. This will most likely result in a failure", - s.name, - ) - } - decl - } else { - newMethodDeclaration( - name = s.name, - recordDeclaration = recordDeclaration, - isStatic = false, - rawNode = s, - ) - } - } else { - newFunctionDeclaration(name = s.name, rawNode = s) - } - frontend.scopeManager.enterScope(result) - - addAsyncWarning(s, result) - - // Handle decorators (which are translated into CPG "annotations") - result.annotations += handleAnnotations(s) - - // Handle return type and calculate function type - if (result is ConstructorDeclaration) { - // Return type of the constructor is always its record declaration type - result.returnTypes = listOf(recordDeclaration?.toType() ?: unknownType()) - } else { - result.returnTypes = listOf(frontend.typeOf(s.returns)) - } - result.type = FunctionType.computeType(result) - - handleArguments(s.args, result, recordDeclaration) - - if (s.body.isNotEmpty()) { - result.body = makeBlock(s.body, parentNode = s) - } - - frontend.scopeManager.leaveScope(result) - frontend.scopeManager.addDeclaration(result) - - return wrapDeclarationToStatement(result) - } - /** * Translates a Python [`Global`](https://docs.python.org/3/library/ast.html#ast.Global) into a * [LookupScopeStatement]. @@ -941,143 +854,7 @@ class StatementHandler(frontend: PythonLanguageFrontend) : ) } - /** Adds the arguments to [result] which might be located in a [recordDeclaration]. */ - private fun handleArguments( - args: Python.AST.arguments, - result: FunctionDeclaration, - recordDeclaration: RecordDeclaration?, - ) { - // We can merge posonlyargs and args because both are positional arguments. We do not - // enforce that posonlyargs can ONLY be used in a positional style, whereas args can be used - // both in positional and keyword style. - var positionalArguments = args.posonlyargs + args.args - - // Handle receiver if it exists and if it is not a static method - if ( - recordDeclaration != null && - result.annotations.none { it.name.localName == "staticmethod" } - ) { - handleReceiverArgument(positionalArguments, args, result, recordDeclaration) - // Skip the receiver argument for further processing - positionalArguments = positionalArguments.drop(1) - } - - // Handle remaining arguments - handlePositionalArguments(positionalArguments, args) - - args.vararg?.let { handleArgument(it, isPosOnly = false, isVariadic = true) } - args.kwarg?.let { handleArgument(it, isPosOnly = false, isVariadic = false) } - - handleKeywordOnlyArguments(args) - } - - /** - * This method retrieves the first argument of the [positionalArguments], which is typically the - * receiver object. - * - * A receiver can also have a default value. However, this case is not handled and is therefore - * passed with a problem expression. - */ - private fun handleReceiverArgument( - positionalArguments: List, - args: Python.AST.arguments, - result: FunctionDeclaration, - recordDeclaration: RecordDeclaration, - ) { - // first argument is the receiver - val recvPythonNode = positionalArguments.firstOrNull() - if (recvPythonNode == null) { - result.additionalProblems += newProblemExpression("Expected a receiver", rawNode = args) - } else { - val tpe = recordDeclaration.toType() - val recvNode = - newVariableDeclaration( - name = recvPythonNode.arg, - type = tpe, - implicitInitializerAllowed = false, - rawNode = recvPythonNode, - ) - - // If the number of defaults equals the number of positional arguments, the receiver has - // a default value - if (args.defaults.size == positionalArguments.size) { - val defaultValue = - args.defaults.getOrNull(0)?.let { frontend.expressionHandler.handle(it) } - defaultValue?.let { - frontend.scopeManager.addDeclaration(recvNode) - result.additionalProblems += - newProblemExpression("Receiver with default value", rawNode = args) - } - } - - when (result) { - is ConstructorDeclaration, - is MethodDeclaration -> result.receiver = recvNode - else -> - result.additionalProblems += - newProblemExpression( - problem = - "Expected a constructor or method declaration. Got something else.", - rawNode = result, - ) - } - } - } - - /** - * This method extracts the [positionalArguments] including those that may have default values. - * - * In Python only the arguments with default values are stored in `args.defaults`. - * https://docs.python.org/3/library/ast.html#ast.arguments - * - * For example: `def my_func(a, b=1, c=2): pass` - * - * In this case, `args.defaults` contains only the defaults for `b` and `c`, while `args.args` - * includes all arguments (`a`, `b` and `c`). The number of arguments without defaults is - * determined by subtracting the size of `args.defaults` from the total number of arguments. - * This matches each default to its corresponding argument. - * - * From the Python docs: "If there are fewer defaults, they correspond to the last n arguments." - */ - private fun handlePositionalArguments( - positionalArguments: List, - args: Python.AST.arguments, - ) { - val nonDefaultArgsCount = positionalArguments.size - args.defaults.size - - for (idx in positionalArguments.indices) { - val arg = positionalArguments[idx] - val defaultIndex = idx - nonDefaultArgsCount - val defaultValue = - if (defaultIndex >= 0) { - args.defaults.getOrNull(defaultIndex)?.let { - frontend.expressionHandler.handle(it) - } - } else { - null - } - handleArgument(arg, isPosOnly = arg in args.posonlyargs, defaultValue = defaultValue) - } - } - - /** - * This method extracts the keyword-only arguments from [args] and maps them to the - * corresponding function parameters. - */ - private fun handleKeywordOnlyArguments(args: Python.AST.arguments) { - for (idx in args.kwonlyargs.indices) { - val arg = args.kwonlyargs[idx] - val default = args.kw_defaults.getOrNull(idx) - handleArgument( - arg, - isPosOnly = false, - isKwoOnly = true, - defaultValue = default?.let { frontend.expressionHandler.handle(it) }, - ) - } - } - - private fun handleAnnotations( + internal fun handleAnnotations( node: Python.AST.NormalOrAsyncFunctionDef ): Collection { return handleDeclaratorList(node, node.decorator_list) @@ -1155,7 +932,7 @@ class StatementHandler(frontend: PythonLanguageFrontend) : * itself does not have a code/location, we need to employ [codeAndLocationFromChildren] on the * [parentNode]. */ - private fun makeBlock( + internal fun makeBlock( stmts: List, parentNode: Python.AST.WithLocation, ): Block { @@ -1179,37 +956,6 @@ class StatementHandler(frontend: PythonLanguageFrontend) : return result } - /** - * This function creates a [newParameterDeclaration] for the argument, setting any modifiers - * (like positional-only or keyword-only) and [defaultValue] if applicable. - */ - internal fun handleArgument( - node: Python.AST.arg, - isPosOnly: Boolean = false, - isVariadic: Boolean = false, - isKwoOnly: Boolean = false, - defaultValue: Expression? = null, - ) { - val type = frontend.typeOf(node.annotation) - val arg = - newParameterDeclaration( - name = node.arg, - type = type, - variadic = isVariadic, - rawNode = node, - ) - defaultValue?.let { arg.default = it } - if (isPosOnly) { - arg.modifiers += MODIFIER_POSITIONAL_ONLY_ARGUMENT - } - - if (isKwoOnly) { - arg.modifiers += MODIFIER_KEYWORD_ONLY_ARGUMENT - } - - frontend.scopeManager.addDeclaration(arg) - } - /** * Wrap a declaration in a [DeclarationStatement] * @@ -1226,7 +972,7 @@ class StatementHandler(frontend: PythonLanguageFrontend) : * Checks whether [mightBeAsync] implements the [IsAsync] interface and adds a warning to the * corresponding [parentNode] stored in [Node.additionalProblems]. */ - private fun addAsyncWarning(mightBeAsync: Python.AST.AsyncOrNot, parentNode: Node) { + internal fun addAsyncWarning(mightBeAsync: Python.AST.AsyncOrNot, parentNode: Node) { if (mightBeAsync is IsAsync) { parentNode.additionalProblems += newProblemDeclaration(