I can´t explain this weird parser ERROR, please help

Godot Version

4.4.1

Question

Hello,

as you can tell from the title, I am facing a very strange parser error. One that I´ve tried to understand many times but have failed to do so.

Here is the error:
image

Now, I know this means that the program couldn´t find the BASE_NICE_2_ACHIEVEMENT variable. But the problem is that it´s stored inside an autoload (The autoloads at the time of the error are shown below). Quite humorously the next one to load is ResourceCopies, which is exactly where BASE_NICE_2_ACHIEVEMENT is located. So far so good. The strange thing though is that the script where the error occurs only exists in one place, the Game autoload. The Game autoload, as you can see, isn´t loaded either though and should be loaded after ResourceCopies. So it would´t make any sense as to why a script that isn´t even loaded is trying to load something else.


This isn´t even the strange part though. The variable that the program tries to grab but fails is a type Roll_Filter_Achievement_Base_Class(Custom type, obviously). And inside that class I am declaring a variable of type Die(Also custom type). I´m just declaring it, not assigning it a value. And the curious thing is, if I don´t declare this type, the error doesn´t occur.

And this is where it stops making any sense to me. After all why would adding a type hint change the loading order of autoloads. Maybe it doesn´t, maybe it always loads like that and the type hint just makes the script invalid or something, and so the variable as well. I don´t know, the engine doesn´t tell me anything else.

Now, before you ask. Yes the Die class is used successfully in other parts of my program. It doesn´t cause any parser errors there. It´s just in this one specific case. And what bugs me the most, is that it´s not even an instanced Die, it´s just a type hint. How can that cause an error like this?

“Could not resolve external class member” means there is some parse error in that class member’s script, not that it’s not found. Godot needs to know/be able to work with the variable type at compile time (before actually assigning a value) which explains why the error is thrown even when the node isn’t instantiated.

But I can’t explain why the error only appears in that script… maybe reloading the project will fix it?

I deal with this all the time - sometimes compiler is stuck in a recursive loop when it finds a class that it can’t parse properly, e.g. due to syntax error. Then this will cause other classes to also fail the parsing, and it might propagate to other classes etc. Then even if you fix the original syntax error that started this chain, the compiler is lost and will still show you some parsing errors.

Simply reloading the project will most likely resolve this issue completely, if there are no actual issues with the code anymore.
Or going into the classes script (ctrl + click on the class type), saving that script and returning to the other class where the parse error appeared - usually works too, it’s faster than reloading the project, but is a bit less intuitive.

I actually encountered this issue yesterday and have since restarted the project multiple times and obviously also restarted my computer.

What is in your Die class script?
And the BASE_NICE_2_ACHIEVEMENT class script?

Here are the two scripts you asked for. Thanks for helping btw.

Die.gd


extends Resource

class_name Die

signal Highest_Roll_Possible_Rolled
signal Rolls_Per_Throw_Updated

const MAXIMUM_ROLLS_PER_THROW : int = 6

@export var Number_Of_Sides : int = 20
@export var Rolls_Per_Throw : int = 1:
	set(Value):
		Rolls_Per_Throw = Value
		Rolls_Per_Throw_Updated.emit()

@export var Total_Roll_Amount : int = 0
@export var Total_Roll_Sum : float = 0

@export var Previous_Rolls : Array[Die_Roll] = []
@export var All_Previous_Rolls : Array[Array] = []

@export var All_Rolls_This_Session : Array[Array]
@export var Total_Roll_Amount_This_Session : int = 0
@export var Total_Roll_Sum_This_Session : float = 0

@export var Settings : Die_Settings = Die_Settings.new()
@export var Should_Show_Only_Roll_From_This_Session_In_Roll_Log : bool = false

@export var Highest_Roll_Streak : int = 0
@export var Lowest_Roll_Streak : int = 0

func Get_Roll_Average() -> float:
	if Total_Roll_Amount == 0:
		return 0.0
	return Total_Roll_Sum / Total_Roll_Amount

func Get_Roll_Average_This_Session() -> float:
	if Total_Roll_Amount_This_Session == 0:
		return 0.0
	return Total_Roll_Sum_This_Session / Total_Roll_Amount_This_Session

func Change_Amount_Of_Sides(New_Amount : int) -> void:
	Number_Of_Sides = New_Amount
	Total_Roll_Amount = 0
	Total_Roll_Sum  = 0
	Previous_Rolls = []
	All_Previous_Rolls = []
	Reset_Session_Values()
	Game.Master_SF.Achievements.Dimensional_Paradox.Try_To_Unlock(New_Amount)
	Game.Master_SF.Achievements.Nice.Try_To_Unlock(New_Amount)
	

func Roll(Cosmetic : bool = false) -> Die_Roll:
	var New_Roll : Die_Roll = Die_Roll.new()
	
	var Rolled_Amount : int = randi_range(1, Number_Of_Sides)
	New_Roll.Value = Rolled_Amount
	
	if not Cosmetic:
		Total_Roll_Amount += 1
		Total_Roll_Sum  += Rolled_Amount
		
		Total_Roll_Amount_This_Session += 1
		Total_Roll_Sum_This_Session += Rolled_Amount
			
		Game.Master_SF.Total_Times_Rolled += 1
		Game.Master_SF.Achievements.First_Steps.Try_To_Unlock(Game.Master_SF.Total_Times_Rolled)
		Game.Master_SF.Achievements.Roll_Trainee.Try_To_Unlock(Game.Master_SF.Total_Times_Rolled)
		Game.Master_SF.Achievements.Roll_Pro.Try_To_Unlock(Game.Master_SF.Total_Times_Rolled)
		Game.Master_SF.Achievements.Roll_God.Try_To_Unlock(Game.Master_SF.Total_Times_Rolled)
		Game.Master_SF.Achievements.Touch_Grass.Try_To_Unlock(Game.Master_SF.Total_Times_Rolled)
		
		
		
		if Rolled_Amount == Number_Of_Sides:
			Highest_Roll_Possible_Rolled.emit()
			Highest_Roll_Streak += 1
		else:
			Highest_Roll_Streak = 0
			
		if Rolled_Amount == 1:
			Lowest_Roll_Streak += 1
		else:
			Lowest_Roll_Streak = 0
		
		
		New_Roll.Highest_Roll_In_A_Row = Highest_Roll_Streak
		New_Roll.Lowest_Roll_In_A_Row = Lowest_Roll_Streak
		
		Game.Master_SF.Achievements.Nice_2.Try_To_Unlock(New_Roll, Number_Of_Sides)
		Game.Master_SF.Achievements.Primal.Try_To_Unlock(New_Roll, Number_Of_Sides)
		Game.Master_SF.Achievements.Primal_Prime.Try_To_Unlock(New_Roll, Number_Of_Sides)
		Game.Master_SF.Achievements.Lucky_Bastard.Try_To_Unlock(New_Roll, Number_Of_Sides)
		Game.Master_SF.Achievements.Poor_You.Try_To_Unlock(New_Roll, Number_Of_Sides)
		
	
	return New_Roll

func Get_Rolls(Cosmetic : bool = false) -> Array[Die_Roll]:
	var Rolls : Array[Die_Roll]
	
	for i in Rolls_Per_Throw:
		Rolls.append(Roll(Cosmetic))
		
	if not Cosmetic:
		Previous_Rolls = Rolls
		All_Previous_Rolls.append(Rolls)
		All_Rolls_This_Session.append(Rolls)
	
	return Rolls

func Reset_Session_Values() -> void:
	All_Rolls_This_Session = []
	Total_Roll_Amount_This_Session = 0
	Total_Roll_Sum_This_Session = 0

This is not the inhereted script, but instead the parent class where I inserted the Die variable seen in my original post. (Here the test : Die variable has been removed)

Roll_Filter_Achievement_Base_Class.gd

extends Achievement

class_name Roll_Filter_Achievement_Base_Class

@export var AMOUNT_OF_CONSECUTIVE_ROLLS_NEEDED : int = 0
@export var REQUIRED_AMOUNT_OF_SIDES : int = 1
@export var Consecutive_Rolls : Array[Die_Roll] = []

func Try_To_Unlock(Roll : Die_Roll, Amount_Of_Sides : int) -> void:
	if Unlocked or Amount_Of_Sides < REQUIRED_AMOUNT_OF_SIDES:
		return
	if Filter(Roll, Amount_Of_Sides):
		Consecutive_Rolls.append(Roll)
	else:
		Consecutive_Rolls.clear()
		
	if Consecutive_Rolls.size() >= AMOUNT_OF_CONSECUTIVE_ROLLS_NEEDED:
		Unlock()
	Unlock_Attempt_Was_Made.emit()
		

func Filter(Roll : Die_Roll, Amount_Of_Sides :int) -> bool:
	if Roll and Amount_Of_Sides:
		return true
	return false

func Get_Unlocking_Progess() -> float:
	if Unlocked:
		return 1.0
	return float(Consecutive_Rolls.size()) / float(AMOUNT_OF_CONSECUTIVE_ROLLS_NEEDED)

Your Die class extends 2 classes - Resource and Achievement. This can’t be done currently in GDScript, you can only inherit one class.

No, sry. The extends Achievement is inside the other script. My mistake. One sec I´ll edit my message. (I edited it now)

So now when you add this line var test: Die to your script - what error do you get?

When I add it to the Roll_Filter_Achievement_Base_Class.gd script the same error occurs. (That´s where I placed it before)

image

And where is BASE_NICE_2_ACHIEVEMENT script? or any reference to it in your code? Because in these 2 scripts you pasted it’s not mentioned anywhere

This is it´s script. It extends the before pasted script.

extends Roll_Filter_Achievement_Base_Class

class_name Nice_2_Achievement

func Filter(Roll : Die_Roll, _Amount_Of_Sides : int) -> bool:
	if Consecutive_Rolls.is_empty():
		if Roll.Value == 6:
			return true
		return false
	if Consecutive_Rolls[0].Value == 6:
		if Roll.Value == 9:
			return true
		return false
	return false

All though I don´t think this matters, since other Resources extending from the script containing the

var test : Die

cause the same issue.

This is still not BASE_NICE_2_ACHIEVEMENT, this is Nice_2_Achievement.
Use ctrl+shift+r in your project and search for “BASE_NICE_2_ACHIEVEMENT”

BASE_NICE_2_ACHIEVEMENT is the variables name. It extends Nice_2_Achievement. Sorry for the confusion.

const BASE_NICE_2_ACHIEVEMENT : Nice_2_Achievement = preload("res://Achievements/Nice_2_Achievement/Nice_2_Achievement_Base_Copy.tres")

Where is this line specifically? Can you paste the whole script?

const BASE_NICE_2_ACHIEVEMENT : Nice_2_Achievement = preload("res://Achievements/Nice_2_Achievement/Nice_2_Achievement_Base_Copy.tres")

I expect there might be some cyclic reference maybe.

extends Node

const BASE_ROLL_TRAINEE_ACHIEVEMENT : Roll_Trainee_Achievement = preload("res://Achievements/Roll_Trainee_Achievement/Roll_Trainee_Base_Copy.tres")
const BASE_DIMENSIONAL_PARADOX_ACHIEVEMENT : Dimensional_Paradox_Achievement = preload("res://Achievements/Dimensional_Paradox_Achievement/Dimensional_Paradox_Achievement.tres")
const BASE_ROLL_PRO_ACHIEVEMENT : Roll_Pro_Achievement = preload("res://Achievements/Roll_Pro_Achievement/Roll_Pro_Achievement.tres")
const BASE_ROLL_GOD_ACHIEVEMENT : Roll_God_Achievement = preload("res://Achievements/Roll_God_Achievement/Roll_God_Achievement_Base_Copy.tres")
const BASE_TOUCH_GRASS_ACHIEVEMENT : Touch_Grass_Achievement = preload("res://Achievements/Touch_Grass_Achievement/Touch_Grass_Achievement.tres")
const BASE_FIRST_STEPS_ACHIEVEMENT : First_Steps_Achievement = preload("res://Achievements/First_Steps_Achievement/First_Steps_Achievement.tres")
const BASE_SAVIOUR_ACHIEVEMENT : Saviour_Achievement = preload("res://Achievements/Saviour_Achievement/Saviour_Achievement.tres")
const BASE_NICE_ACHIEVEMENT : Nice_Achievement = preload("res://Achievements/Nice_Achievement/Nice_Achievement_Base_Copy.tres")
const BASE_NICE_2_ACHIEVEMENT : Nice_2_Achievement = preload("res://Achievements/Nice_2_Achievement/Nice_2_Achievement_Base_Copy.tres")
const BASE_PRIMAL_ACHIEVEMENT : Primal_Achievement = preload("res://Achievements/Primal_Achievement/Primal_Achievement.tres")
const BASE_PRIMAL_PRIME_ACHIEVEMENT : Primal_Prime_Achievement = preload("res://Achievements/Primal_Prime_Achievement/Primal_Prime_Achievement_Base_Copy.tres")
const BASE_LUCKY_BASTARD_ACHIEVEMENT : Lucky_Bastard_Achievement = preload("res://Achievements/Lucky_Bastard/Lucky_Bastard_Achievement.tres")
const BASE_POOR_YOU_ACHIEVEMENT : Poor_You_Achievement = preload("res://Achievements/Poor_You_Achievement/Poor_You_Achievement_Base_Copy.tres")
const BASE_THATS_DOPE_ACHIEVEMENT : ThatS_Dope_Achievement = preload("res://Achievements/ThatS_Dope_Achievement/ThatS_Dope_Achievement_Base_Copy.tres")
const BASE_DEJAVU_ACHIEVEMENT : Dejavu_Achievement = preload("res://Achievements/Dejavu_Achievement/Dejavu_Achievement_Base_Copy.tres")

This is inside the before mentioned ResourceCopies autoload. It´s just storing a bunch of Resources for later use.

Sorry, but it’s pretty hard to find the rootcause without access to the full codebase.
My best guess is that adding the var test: Die line, you’re creating a cyclic dependency between your classes (e.g. Die → Die_Roll → … → Roll_Filter_Achievement_Base_Class → Die), which the Engine can’t resolve and throws this error. You might need to restructure your code to avoid this cyclic dependency.

1 Like

Don´t worry. It´s very nice of you to have spent that much time trying to help me. But yeah that would have been my guess too, since I´ve have a similiar issue before and there that was the solution. But here I can´t find any. I just checked the Die_Roll class, just in case, and it´s just int´s so no luck there unfortunately.

1 Like

Just FYI - I also tried reproducing this error on my end with some minimal classes, but I couldn’t get it at all. I deliberately added some obvious cyclic dependencies, but it all worked fine without any errors.
I’m out of ideas :frowning: