Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get_method_list() returns an incorrect info about custom functions #33624

Closed
dalexeev opened this issue Nov 14, 2019 · 11 comments · Fixed by #81079
Closed

get_method_list() returns an incorrect info about custom functions #33624

dalexeev opened this issue Nov 14, 2019 · 11 comments · Fixed by #81079
Milestone

Comments

@dalexeev
Copy link
Member

Godot version:
3.1.1.stable.official
3.2.beta1.official

OS/device including version:
Linux

Issue description:
See the title above and the example below.

Steps to reproduce:

extends Node

func my_func(s: String, my_arg := 42000) -> bool:
    return my_arg % 7 == 0

func print_info(i):
    var args = ''
    for j in i.args:
        args += '%s:%s;' % [j.name, j.type]
    print('name=%s args=%s default_args=%s return.type=%s' %
            [i.name, args, i.default_args, i.return.type])

func _ready():
    for i in get_method_list():
        if i.name == 'my_func' || i.name == 'connect':
            print_info(i)
    for i in get_script().get_script_method_list():
        if i.name == 'my_func':
            print_info(i)

# name=connect args=signal:4;target:17;method:4;binds:19;flags:2;
    # default_args=[[], 0] return.type=2
# name=my_func args=arg0:0;arg1:0; default_args=[] return.type=0
# name=my_func args=:4;:2; default_args=[] return.type=1

Minimal reproduction project:

@Dbeckett23
Copy link

Hello, I have been looking into to this issue and want to clarify that the output should be:
name=my_func args=s:4;my_arg:2; default_args=[4] return.type=1
where [4] represents the type of the default argument.
or:
name=my_func args=s:4;my_arg:2; default_args=[42000] return.type=1
where [42000] represents the value of the default argument.

I'm assuming the second scenario, but any correction or advice would be appreciated.

@dalexeev
Copy link
Member Author

dalexeev commented Dec 4, 2019

I'm assuming the second scenario

Yes, see the output for the connect() method:

default_args=[[], 0]

These are the default values of optional arguments binds and flags.

@MikeSchulze
Copy link

Hi the method 'get_method_list()' uses also the wrong "flags" for script added functions
The doc says

enum MethodFlags {
METHOD_FLAG_FROM_SCRIPT = 64
#Deprecated method flag, unused.
--
}

but the info contains the value 65 for the property "flags" where is not defined in the enum

Second problem is overwriten private functions like _init()or _ready() also listed under this flag

I would prefear to update the enum and add a FLAG for private functions

@akien-mga
Copy link
Member

but the info contains the value 65 for the property "flags" where is not defined in the enum

Those flags are powers of two and meant to be combined, so 65 means it's 64 | 1.

@MikeSchulze
Copy link

Ok thanks, but still some flags are marked as deprecated and no flag for static and private functions exists

@MikeSchulze
Copy link

MikeSchulze commented Oct 17, 2020

This bug is still valid on Godot Engine v3.2.3.stable.mono.official
The return information is also wrong, is just empty for custom functions

class_name GdTest
extends Resource

func success_count() -> int:
	return _success_count
{args:[], default_args:[...], flags:65, id:0, name:success_count, return:{class_name:, hint:0, hint_string:, name:, type:0, usage:7}}

The return type must be TYPE_INT

When this bug will be fixed?
Please don't fix only in the master branch, is still need in the 3.2.x branch as well
It still blocks me to continue with my project.
I need to determine a function return type without executiong the function.

@MikeSchulze
Copy link

Hi any news here?
Exists a workaround to collect all infos for functions?

I need to collection info about arguments of custom functions

@Gallilus
Copy link
Contributor

Hi when working on a other item I see that Script..get_script_method_list() Has different functionality between GDscript and VisualScript.

func _subcall(arg10:= "5.0", arg2:=Vector3.ONE) -> Vector3:
	return arg2

{args:[{class_name:, hint:0, hint_string:, name:, type:4, usage:7}, {class_name:, hint:0, hint_string:, name:, type:7, usage:7}], default_args:[], flags:1, id:0, name:_subcall, return:{class_name:, hint:0, hint_string:, name:, type:7, usage:7}}

While
image

{args:[{class_name:, hint:0, hint_string:, name:arg10, type:4, usage:7}, {class_name:, hint:0, hint_string:, name:arg2, type:7, usage:7}], default_args:[], flags:1, id:0, name:_subcall, return:{class_name:, hint:0, hint_string:, name:, type:0, usage:7}

Notice that Name is missing in the GD args.
I have not tested NativeScript::get_script_method_list(List* p_list) or PluginScript::get_script_method_list(List* r_methods)

I have successfully tried code below. And assume this is not the best way to do this.

void GDScript::get_script_method_list(List<MethodInfo> *p_list) const {
...
			for (int i = 0; i < func->get_argument_count(); i++) {
			//	PropertyInfo arg;
			//	arg.type = func->get_argument_type(i).builtin_type;
			//	arg.name = func->get_argument_name(i);
			//	mi.arguments.push_back(arg);
				mi.arguments.push_back(func->get_argument_type(i));
...}

Don't know if it helps just wanted to share my finding.

PS: In the GDscript example I would expect the default args

@CrezyDud

This comment has been minimized.

@Calinou
Copy link
Member

Calinou commented Nov 30, 2021

@CrezyDud Please don't bump issues without contributing significant new information. Use the 👍 reaction button on the first post instead.

@batomow
Copy link

batomow commented Jan 2, 2022

I wrote a parser that uses the scripts source code and returns all the method definitions, the return type and their argument types. Just in case anyone needs it ill post it here.
Edited: "Added args Dictionary instead of array, add return_type string to constructor functions"

class_name TU extends Reference

const itypes = {
0: "Variant/void",
1: "bool",
2: "int",
3: "float",
4: "String",
5: "Vector2",
6: "Rect2",
7: "Vector3",
8: "Transform2D",
9: "Plane",
10: "Quat",
11: "AABB",
12: "Basis",
13: "Transform",
14: "Color",
15: "NodePath",
16: "RID",
17: "Object",
18: "Dictionary",
19: "Array",
20: "PoolByteArray",
21: "PoolIntArray",
22: "PoolRealArray",
23: "PoolStringArray",
24: "PoolVector2Array",
25: "PoolVector3Array",
26: "PoolColorArray",
27: "MAX"
}

const types = {
"void": 0,
"Variant": 0,
"bool": 1,
"int": 2,
"float": 3,
"String": 4,
"Vector2": 5,
"Rect2": 6,
"Vector3": 7,
"Transform2D": 8,
"Plane": 9,
"Quat": 10,
"AABB": 11,
"Basis": 12,
"Transform":13,
"Color": 14,
"NodePath": 15,
"RID": 16,
"Object": 17,
"Dictionary": 18,
"Array": 19,
"PoolByteArray": 20,
"PoolIntArray": 21,
"PoolRealArray": 22,
"PoolStringArray": 23,
"PoolVector2Array": 24,
"PoolVector3Array": 25,
"PoolColorArray": 26,
"MAX": 27
}


static func arg(argument_name:String, type:int=TYPE_NIL, default_value=null)->Dictionary: 
	return {
		"name": argument_name, 
		"type": itypes[type],
		"typeof": type , 
		"default_value": default_value
	}

static func fun(function_name:String, return_type:int=TYPE_NIL, arguments:Array=[])->Dictionary:
	return {
		"name": function_name,
		"return_type": itypes[return_type],
		"return_typeof": return_type, 
		"args": arguments
	}

static func parse_source_code(source_code:String)->Dictionary: 
	var methods_regex = RegEx.new()
	methods_regex.compile("func\\s(\\_*[\\w\\d]*)*\\s?\\(((\\s*\\n*\\r*\\t*)(_*[\\w\\d]*)*\\s*(\\:\\s?[\\w\\d]*)?\\=?(\\s?\\_*[\\w\\d]*)\\,?)*\\)(\\-\\>[\\w\\d]*)?\\:")
	var methods:Array = methods_regex.search_all(source_code)
	var stripped_methods = {}
	for m in methods: 
		var chunks = m.get_string().strip_escapes().replace(" ", "").trim_prefix("func").trim_suffix(":").replace("(", " ").replace(")", " ").split(" ")
		var method_name = chunks[0]
		var return_type = chunks[-1].trim_prefix("->")
		var raw_args = chunks[1].split(",")
		var args = {}

		for raw in raw_args: 
			var default_value = null
			var name = null
			var type = null
			var arg = {}

			var split1 = raw.split("=")
			if split1.size() > 1:  
				var split2 = split1[0].split(":")
				default_value = split1[1] if split1[1] else null
				name = split2[0] if split2[0] else null
				if split2.size() > 1:  #has type
					type = split2[1] if split2[1] else null
			else: 
				var split2 = split1[0].split(":")
				name = split2[0] if split2[0] else null
				if split2.size() > 1:  #only type
					type = split2[1] if split2[1] else null
			if not name: 
				continue
			if type: 
				arg["type"] = type
				arg["typeof"] = types[type]
			if default_value: 
				arg["default_value"] = default_value
			args[name] = arg

		stripped_methods[method_name]={
			"return_type": return_type if return_type else "void", 
			"return_typeof": types[return_type] if return_type else 0,
			"args": args 
		}
	return stripped_methods

Here is how to use it, I used a _format function that just makes the output a string in json format thats nice and readable. The result object looks like the one in the output.

func _format(variable): 
	return JSONBeautifier.beautify_json(var2str(variable))

func before(): 
	var h = HashMap.new()
	var script = h.get_script()
	var result = ScriptParser.parse(script.source_code)
	print(_format(result))

output


{
	"_init": {
		"args": {
			"cellsize": {
				"default_value": "DEFAULT_CELLSIZE",
				"type": "int",
				"typeof": 2
			}
		},
		"return_type": "void",
		"return_typeof": 0
	},
	"add_object": {
		"args": {
			"obj": {
				
			}
		},
		"return_type": "int",
		"return_typeof": 2
	},
	"clear": {
		"args": {
			
		},
		"return_type": "void",
		"return_typeof": 0
	},
	"get_hash": {
		"args": {
			"position": {
				"type": "Vector2",
				"typeof": 5
			}
		},
		"return_type": "Dictionary",
		"return_typeof": 18
	},
	"get_neighbors": {
		"args": {
			"obj": {
				
			},
			"r": {
				"default_value": "1",
				"type": "int",
				"typeof": 2
			}
		},
		"return_type": "Array",
		"return_typeof": 19
	},
	"get_objects": {
		"args": {
			"x": {
				"type": "int",
				"typeof": 2
			},
			"y": {
				"type": "int",
				"typeof": 2
			}
		},
		"return_type": "Array",
		"return_typeof": 19
	},
	"get_objectsv": {
		"args": {
			"coords": {
				"type": "Vector2",
				"typeof": 5
			}
		},
		"return_type": "Array",
		"return_typeof": 19
	},
	"rehash_objects": {
		"args": {
			
		},
		"return_type": "void",
		"return_typeof": 0
	},
	"remove_object": {
		"args": {
			"obj": {
				
			}
		},
		"return_type": "void",
		"return_typeof": 0
	}
}

This is the source code that i used in the above example:

class_name HashMap extends Reference

var cellsize:int
const DEFAULT_CELLSIZE:int = 48
var objects:Array = []
var map:Dictionary = {}

func _init(c := DEFAULT_CELLSIZE):
	cellsize = 0 if c < 0 else c

func get_hash(position:Vector2)->Dictionary:
	var x = int(position.x/cellsize)
	var y = int(position.y/cellsize)
	return {
		"key":"(%s,%s)" % [x, y], 
		"coords": Vector2(x, y)
	}

func add_object(obj)->void: 
	if "global_position" in obj:
		if not obj in objects: 
			objects.push_back(obj)
		else:
			push_warning("object not added")
	else:
		push_error("object missing global_position property")

func remove_object(obj)->void: 
	objects.erase(obj)

func get_objects(
	x:int, 
	y: int = 2,
	z = 10
	)->Array:
	var key = "(%s,%s)" % [int(x), int(y)]
	if key in map: 
		return map[key]
	return []

func get_objectsv(coords:Vector2)->Array: 
	var key = "(%s,%s)" % [int(coords.x), int(coords.y)]
	if key in map: 
		return map[key]
	return []

func clear()->void: 
	objects = []
	map = {}

func rehash_objects()->void: 
	map = {}
	for o in objects: 
		var key = get_hash(o.global_position).key
		if not key in map: 
			map[key] = []
		map[key].push_back(o)

func get_neighbors(obj, r=1)->Array: 
	var neighbors = []
	var coords = get_hash(obj.global_position).coords
	for x in range(-r, r+1): 
		for y in range( -r, r+1): 
			var key = "(%s,%s)"%[coords.x + x, coords.y + y]
			if key in map: 
				neighbors += map[key]
	neighbors.erase(obj)
	return neighbors

You can even define your test definitions (for testing) like this:

##Here I changed the name ScriptParser.gd for TU.gd (for testing utils) just to make this less verbose. 
var method_definitions = [
	TU.fun("_init", TYPE_NIL, [
		TU.arg("cellsize", TYPE_INT)
	]),
	TU.fun("clear", TYPE_NIL),
	TU.fun("get_hash", TYPE_DICTIONARY, [
		TU.arg("position", TYPE_VECTOR2)
	]),
	TU.fun("add_object",TYPE_NIL, [
		TU.arg("object", TYPE_NIL)
	]), 
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants