Browse Source

Merge pull request #46713 from neikeq/csharp-source-generators-init

Add C# source generator for ScriptPathAttribute
Rémi Verschelde 4 years ago
parent
commit
15bd2bf03f
41 changed files with 652 additions and 1350 deletions
  1. 3 0
      modules/mono/Directory.Build.props
  2. 6 0
      modules/mono/SdkPackageVersions.props
  3. 17 7
      modules/mono/build_scripts/godot_net_sdk_build.py
  4. 60 60
      modules/mono/csharp_script.cpp
  5. 20 14
      modules/mono/csharp_script.h
  6. 18 0
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
  7. 15 25
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
  8. 0 22
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
  9. 0 1
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt
  10. 3 0
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
  11. 5 0
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
  12. 15 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs
  13. 11 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs
  14. 31 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
  15. 33 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
  16. 86 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
  17. 40 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
  18. 7 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props
  19. 9 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
  20. 156 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
  21. 0 47
      modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
  22. 3 3
      modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets
  23. 0 31
      modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
  24. 0 4
      modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
  25. 2 1
      modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs
  26. 0 86
      modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
  27. 0 3
      modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
  28. 0 8
      modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
  29. 0 61
      modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
  30. 0 47
      modules/mono/editor/editor_internal_calls.cpp
  31. 0 753
      modules/mono/editor/script_class_parser.cpp
  32. 0 108
      modules/mono/editor/script_class_parser.h
  33. 22 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs
  34. 9 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs
  35. 15 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs
  36. 3 0
      modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
  37. 1 0
      modules/mono/mono_gd/gd_mono.cpp
  38. 39 64
      modules/mono/mono_gd/gd_mono_assembly.cpp
  39. 8 5
      modules/mono/mono_gd/gd_mono_assembly.h
  40. 10 0
      modules/mono/mono_gd/gd_mono_cache.cpp
  41. 5 0
      modules/mono/mono_gd/gd_mono_cache.h

+ 3 - 0
modules/mono/Directory.Build.props

@@ -0,0 +1,3 @@
+<Project>
+  <Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
+</Project>

+ 6 - 0
modules/mono/SdkPackageVersions.props

@@ -0,0 +1,6 @@
+<Project>
+  <PropertyGroup>
+    <PackageVersion_Godot_NET_Sdk>4.0.0-dev4</PackageVersion_Godot_NET_Sdk>
+    <PackageVersion_Godot_SourceGenerators>4.0.0-dev1</PackageVersion_Godot_SourceGenerators>
+  </PropertyGroup>
+</Project>

+ 17 - 7
modules/mono/build_scripts/godot_net_sdk_build.py

@@ -21,6 +21,18 @@ def build_godot_net_sdk(source, target, env):
     # No need to copy targets. The Godot.NET.Sdk csproj takes care of copying them.
 
 
+def get_nupkgs_versions(props_file):
+    import xml.etree.ElementTree as ET
+
+    tree = ET.parse(props_file)
+    root = tree.getroot()
+
+    return {
+        "Godot.NET.Sdk": root.find("./PropertyGroup/PackageVersion_Godot_NET_Sdk").text.strip(),
+        "Godot.SourceGenerators": root.find("./PropertyGroup/PackageVersion_Godot_SourceGenerators").text.strip(),
+    }
+
+
 def build(env_mono):
     assert env_mono["tools"]
 
@@ -30,14 +42,12 @@ def build(env_mono):
 
     module_dir = os.getcwd()
 
-    package_version_file = os.path.join(
-        module_dir, "editor", "Godot.NET.Sdk", "Godot.NET.Sdk", "Godot.NET.Sdk_PackageVersion.txt"
-    )
-
-    with open(package_version_file, mode="r") as f:
-        version = f.read().strip()
+    nupkgs_versions = get_nupkgs_versions(os.path.join(module_dir, "SdkPackageVersions.props"))
 
-    target_filenames = ["Godot.NET.Sdk.%s.nupkg" % version]
+    target_filenames = [
+        "Godot.NET.Sdk.%s.nupkg" % nupkgs_versions["Godot.NET.Sdk"],
+        "Godot.SourceGenerators.%s.nupkg" % nupkgs_versions["Godot.SourceGenerators"],
+    ]
 
     targets = [os.path.join(nupkgs_dir, filename) for filename in target_filenames]
 

+ 60 - 60
modules/mono/csharp_script.cpp

@@ -31,6 +31,7 @@
 #include "csharp_script.h"
 
 #include <mono/metadata/threads.h>
+#include <mono/metadata/tokentype.h>
 #include <stdint.h>
 
 #include "core/config/project_settings.h"
@@ -1182,46 +1183,56 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 }
 #endif
 
-void CSharpLanguage::_load_scripts_metadata() {
-	scripts_metadata.clear();
+void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) {
+	if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) {
+		return;
+	}
 
-	String scripts_metadata_filename = "scripts_metadata.";
+	MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute));
+	String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr);
 
-#ifdef TOOLS_ENABLED
-	scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player";
-#else
-#ifdef DEBUG_ENABLED
-	scripts_metadata_filename += "debug";
-#else
-	scripts_metadata_filename += "release";
-#endif
-#endif
+	dotnet_script_lookup_map[path] = DotNetScriptLookupInfo(
+			p_class->get_namespace(), p_class->get_name(), p_class);
+}
 
-	String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename);
+void CSharpLanguage::lookup_scripts_in_assembly(GDMonoAssembly *p_assembly) {
+	if (p_assembly->has_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute))) {
+		MonoObject *attr = p_assembly->get_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute));
+		bool requires_lookup = CACHED_FIELD(AssemblyHasScriptsAttribute, requiresLookup)->get_bool_value(attr);
 
-	if (FileAccess::exists(scripts_metadata_path)) {
-		String old_json;
+		if (requires_lookup) {
+			// This is supported for scenarios where specifying all types would be cumbersome,
+			// such as when disabling C# source generators (for whatever reason) or when using a
+			// language other than C# that has nothing similar to source generators to automate it.
+			MonoImage *image = p_assembly->get_image();
 
-		Error ferr = read_all_file_utf8(scripts_metadata_path, old_json);
+			int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF);
 
-		ERR_FAIL_COND(ferr != OK);
+			for (int i = 1; i < rows; i++) {
+				// We don't search inner classes, only top-level.
+				MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF);
 
-		Variant old_dict_var;
-		String err_str;
-		int err_line;
-		Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line);
-		if (json_err != OK) {
-			ERR_PRINT("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ").");
-			return;
-		}
+				if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) {
+					continue;
+				}
 
-		scripts_metadata = old_dict_var.operator Dictionary();
-		scripts_metadata_invalidated = false;
+				GDMonoClass *current = p_assembly->get_class(mono_class);
+				if (current) {
+					lookup_script_for_class(current);
+				}
+			}
+		} else {
+			// This is the most likely scenario as we use C# source generators
+			MonoArray *script_types = (MonoArray *)CACHED_FIELD(AssemblyHasScriptsAttribute, scriptTypes)->get_value(attr);
 
-		print_verbose("Successfully loaded scripts metadata");
-	} else {
-		if (!Engine::get_singleton()->is_editor_hint()) {
-			ERR_PRINT("Missing scripts metadata file.");
+			int length = mono_array_length(script_types);
+
+			for (int i = 0; i < length; i++) {
+				MonoReflectionType *reftype = mono_array_get(script_types, MonoReflectionType *, i);
+				ManagedType type = ManagedType::from_reftype(reftype);
+				ERR_CONTINUE(!type.type_class);
+				lookup_script_for_class(type.type_class);
+			}
 		}
 	}
 }
@@ -1300,7 +1311,7 @@ void CSharpLanguage::_on_scripts_domain_unloaded() {
 	}
 #endif
 
-	scripts_metadata_invalidated = true;
+	dotnet_script_lookup_map.clear();
 }
 
 #ifdef TOOLS_ENABLED
@@ -3356,45 +3367,34 @@ Error CSharpScript::reload(bool p_keep_state) {
 
 	GD_MONO_SCOPE_THREAD_ATTACH;
 
-	GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly();
-
-	if (project_assembly) {
-		const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path());
-		if (script_metadata_var) {
-			Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"];
-			const Variant *namespace_ = script_metadata.getptr("namespace");
-			const Variant *class_name = script_metadata.getptr("class_name");
-			ERR_FAIL_NULL_V(namespace_, ERR_BUG);
-			ERR_FAIL_NULL_V(class_name, ERR_BUG);
-			GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String());
-			if (klass && CACHED_CLASS(GodotObject)->is_assignable_from(klass)) {
-				script_class = klass;
-			}
-		} else {
-			// Missing script metadata. Fallback to legacy method
-			script_class = project_assembly->get_object_derived_class(name);
+	const DotNetScriptLookupInfo *lookup_info =
+			CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path());
+
+	if (lookup_info) {
+		GDMonoClass *klass = lookup_info->script_class;
+		if (klass) {
+			ERR_FAIL_COND_V(!CACHED_CLASS(GodotObject)->is_assignable_from(klass), FAILED);
+			script_class = klass;
 		}
+	}
 
-		valid = script_class != nullptr;
+	valid = script_class != nullptr;
 
-		if (script_class) {
+	if (script_class) {
 #ifdef DEBUG_ENABLED
-			print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
+		print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
 #endif
 
-			native = GDMonoUtils::get_class_native_base(script_class);
+		native = GDMonoUtils::get_class_native_base(script_class);
 
-			CRASH_COND(native == nullptr);
+		CRASH_COND(native == nullptr);
 
-			update_script_class_info(this);
+		update_script_class_info(this);
 
-			_update_exports();
-		}
-
-		return OK;
+		_update_exports();
 	}
 
-	return ERR_FILE_MISSING_DEPENDENCIES;
+	return OK;
 }
 
 ScriptLanguage *CSharpScript::get_language() const {

+ 20 - 14
modules/mono/csharp_script.h

@@ -66,6 +66,18 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
 
 #define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst))
 
+struct DotNetScriptLookupInfo {
+	String class_namespace;
+	String class_name;
+	GDMonoClass *script_class = nullptr;
+
+	DotNetScriptLookupInfo() {} // Required by HashMap...
+
+	DotNetScriptLookupInfo(const String &p_class_namespace, const String &p_class_name, GDMonoClass *p_script_class) :
+			class_namespace(p_class_namespace), class_name(p_class_name), script_class(p_script_class) {
+	}
+};
+
 class CSharpScript : public Script {
 	GDCLASS(CSharpScript, Script);
 
@@ -390,16 +402,15 @@ class CSharpLanguage : public ScriptLanguage {
 
 	int lang_idx = -1;
 
-	Dictionary scripts_metadata;
-	bool scripts_metadata_invalidated = true;
+	HashMap<String, DotNetScriptLookupInfo> dotnet_script_lookup_map;
+
+	void lookup_script_for_class(GDMonoClass *p_class);
 
 	// For debug_break and debug_break_parse
 	int _debug_parse_err_line = -1;
 	String _debug_parse_err_file;
 	String _debug_error;
 
-	void _load_scripts_metadata();
-
 	friend class GDMono;
 	void _on_scripts_domain_unloaded();
 
@@ -436,18 +447,13 @@ public:
 	void reload_assemblies(bool p_soft_reload);
 #endif
 
-	_FORCE_INLINE_ Dictionary get_scripts_metadata_or_nothing() {
-		return scripts_metadata_invalidated ? Dictionary() : scripts_metadata;
-	}
+	_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }
 
-	_FORCE_INLINE_ const Dictionary &get_scripts_metadata() {
-		if (scripts_metadata_invalidated) {
-			_load_scripts_metadata();
-		}
-		return scripts_metadata;
-	}
+	void lookup_scripts_in_assembly(GDMonoAssembly *p_assembly);
 
-	_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }
+	const DotNetScriptLookupInfo *lookup_dotnet_script(const String &p_script_path) const {
+		return dotnet_script_lookup_map.getptr(p_script_path);
+	}
 
 	String get_name() const override;
 

+ 18 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln

@@ -2,6 +2,12 @@
 Microsoft Visual Studio Solution File, Format Version 12.00
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "Godot.SourceGenerators\Godot.SourceGenerators.csproj", "{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -12,5 +18,17 @@ Global
 		{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 EndGlobal

+ 15 - 25
modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj

@@ -8,43 +8,33 @@
 
     <PackageId>Godot.NET.Sdk</PackageId>
     <Version>4.0.0</Version>
-    <PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl>
+    <PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion>
+    <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl>
+    <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
     <PackageType>MSBuildSdk</PackageType>
     <PackageTags>MSBuildSdk</PackageTags>
+    <PackageLicenseExpression>MIT</PackageLicenseExpression>
     <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
-  </PropertyGroup>
 
-  <PropertyGroup>
-    <NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile>
-    <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn>
+    <!-- Exclude target framework from the package dependencies as we don't include the build output -->
+    <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
+    <IncludeBuildOutput>false</IncludeBuildOutput>
   </PropertyGroup>
 
-  <Target Name="ReadGodotNETSdkVersion" BeforeTargets="BeforeBuild;BeforeRebuild;CoreCompile">
-    <PropertyGroup>
-      <PackageVersion>$([System.IO.File]::ReadAllText('$(ProjectDir)Godot.NET.Sdk_PackageVersion.txt').Trim())</PackageVersion>
-    </PropertyGroup>
-  </Target>
+  <ItemGroup>
+    <!-- Package Sdk\Sdk.props and Sdk\Sdk.targets file -->
+    <None Include="Sdk\Sdk.props" Pack="true" PackagePath="Sdk" Visible="false" />
+    <None Include="Sdk\Sdk.targets" Pack="true" PackagePath="Sdk" Visible="false" />
+    <!-- SdkPackageVersions.props -->
 
-  <Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') " DependsOnTargets="ReadGodotNETSdkVersion">
-    <PropertyGroup>
-      <NuspecProperties>
-        id=$(PackageId);
-        description=$(Description);
-        authors=$(Authors);
-        version=$(PackageVersion);
-        packagetype=$(PackageType);
-        tags=$(PackageTags);
-        projecturl=$(PackageProjectUrl)
-      </NuspecProperties>
-    </PropertyGroup>
-  </Target>
+    <None Include="..\..\..\SdkPackageVersions.props" Pack="true" PackagePath="Sdk" Visible="false" />
+  </ItemGroup>
 
   <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
     <PropertyGroup>
       <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
       <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
     </PropertyGroup>
-    <Copy SourceFiles="$(OutputPath)$(PackageId).$(PackageVersion).nupkg"
-          DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
+    <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
   </Target>
 </Project>

+ 0 - 22
modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec

@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
-  <metadata>
-    <id>$id$</id>
-    <version>$version$</version>
-    <description>$description$</description>
-    <authors>$authors$</authors>
-    <owners>$authors$</owners>
-    <projectUrl>$projecturl$</projectUrl>
-    <requireLicenseAcceptance>false</requireLicenseAcceptance>
-    <license type="expression">MIT</license>
-    <licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
-    <tags>$tags$</tags>
-    <packageTypes>
-      <packageType name="$packagetype$" />
-    </packageTypes>
-    <repository url="$projecturl$" />
-  </metadata>
-  <files>
-    <file src="Sdk\**" target="Sdk" />
-  </files>
-</package>

+ 0 - 1
modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt

@@ -1 +0,0 @@
-4.0.0-dev3

+ 3 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props

@@ -1,4 +1,6 @@
 <Project>
+  <Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
+
   <PropertyGroup>
     <!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. -->
     <GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk>
@@ -94,6 +96,7 @@
     <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
   </PropertyGroup>
 
+  <!-- Godot API references -->
   <ItemGroup>
     <!--
     TODO:

+ 5 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets

@@ -14,4 +14,9 @@
     -->
     <DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants>
   </PropertyGroup>
+
+  <!-- C# source generators -->
+  <ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' ">
+    <PackageReference Include="Godot.SourceGenerators" Version="$(PackageVersion_Godot_SourceGenerators)" />
+  </ItemGroup>
 </Project>

+ 15 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs

@@ -0,0 +1,15 @@
+namespace Godot.SourceGenerators.Sample
+{
+    partial class Bar : Godot.Object
+    {
+    }
+
+    // Foo in another file
+    partial class Foo
+    {
+    }
+
+    partial class NotSameNameAsFile : Godot.Object
+    {
+    }
+}

+ 11 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs

@@ -0,0 +1,11 @@
+namespace Godot.SourceGenerators.Sample
+{
+    partial class Foo : Godot.Object
+    {
+    }
+
+    // Foo again in the same file
+    partial class Foo
+    {
+    }
+}

+ 31 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj

@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.1</TargetFramework>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk -->
+    <GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <!-- The emitted files are not part of the compilation nor design.
+    They're only for peeking at the generated sources. Sometimes the
+    emitted files get corrupted, but that won't break anything. -->
+    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
+    <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj">
+      <Private>False</Private>
+    </ProjectReference>
+    <ProjectReference Include="..\Godot.SourceGenerators\Godot.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
+  </ItemGroup>
+
+  <!-- This file is imported automatically when using PackageReference to
+  reference Godot.SourceGenerators, but not when using ProjectReference -->
+  <Import Project="..\Godot.SourceGenerators\Godot.SourceGenerators.props" />
+
+</Project>

+ 33 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs

@@ -0,0 +1,33 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Godot.SourceGenerators
+{
+    public static class Common
+    {
+        public static void ReportNonPartialGodotScriptClass(
+            GeneratorExecutionContext context,
+            ClassDeclarationSyntax cds, INamedTypeSymbol symbol
+        )
+        {
+            string message =
+                "Missing partial modifier on declaration of type '" +
+                $"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'";
+
+            string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " +
+                                 "declared with the partial modifier or annotated with the " +
+                                 $"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'.";
+
+            context.ReportDiagnostic(Diagnostic.Create(
+                new DiagnosticDescriptor(id: "GODOT-G0001",
+                    title: message,
+                    messageFormat: message,
+                    category: "Usage",
+                    DiagnosticSeverity.Error,
+                    isEnabledByDefault: true,
+                    description),
+                cds.GetLocation(),
+                cds.SyntaxTree.FilePath));
+        }
+    }
+}

+ 86 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs

@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Godot.SourceGenerators
+{
+    static class ExtensionMethods
+    {
+        public static bool TryGetGlobalAnalyzerProperty(
+            this GeneratorExecutionContext context, string property, out string? value
+        ) => context.AnalyzerConfigOptions.GlobalOptions
+            .TryGetValue("build_property." + property, out value);
+
+        private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName)
+        {
+            if (symbol == null)
+                return false;
+
+            while (true)
+            {
+                if (symbol.ToString() == baseName)
+                {
+                    return true;
+                }
+
+                if (symbol.BaseType != null)
+                {
+                    symbol = symbol.BaseType;
+                    continue;
+                }
+
+                break;
+            }
+
+            return false;
+        }
+
+        private static bool IsGodotScriptClass(
+            this ClassDeclarationSyntax cds, Compilation compilation,
+            out INamedTypeSymbol? symbol
+        )
+        {
+            var sm = compilation.GetSemanticModel(cds.SyntaxTree);
+
+            var classTypeSymbol = sm.GetDeclaredSymbol(cds);
+
+            if (classTypeSymbol?.BaseType == null
+                || !classTypeSymbol.BaseType.InheritsFrom(GodotClasses.Object))
+            {
+                symbol = null;
+                return false;
+            }
+
+            symbol = classTypeSymbol;
+            return true;
+        }
+
+        public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectGodotScriptClasses(
+            this IEnumerable<ClassDeclarationSyntax> source,
+            Compilation compilation
+        )
+        {
+            foreach (var cds in source)
+            {
+                if (cds.IsGodotScriptClass(compilation, out var symbol))
+                    yield return (cds, symbol!);
+            }
+        }
+
+        public static bool IsPartial(this ClassDeclarationSyntax cds)
+            => cds.Modifiers.Any(SyntaxKind.PartialKeyword);
+
+        public static bool HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol)
+            => symbol.GetAttributes().Any(attr =>
+                attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr);
+
+        private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } =
+            SymbolDisplayFormat.FullyQualifiedFormat
+                .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted);
+
+        public static string FullQualifiedName(this INamedTypeSymbol symbol)
+            => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal);
+    }
+}

+ 40 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj

@@ -0,0 +1,40 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <LangVersion>8.0</LangVersion>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+  <PropertyGroup>
+    <Description>Core C# source generator for Godot projects.</Description>
+    <Authors>Godot Engine contributors</Authors>
+
+    <PackageId>Godot.SourceGenerators</PackageId>
+    <Version>4.0.0</Version>
+    <PackageVersion>$(PackageVersion_Godot_SourceGenerators)</PackageVersion>
+    <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators</RepositoryUrl>
+    <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
+    <PackageLicenseExpression>MIT</PackageLicenseExpression>
+
+    <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <!-- Generates a package at build -->
+    <IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include the generator as a lib dependency -->
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" />
+  </ItemGroup>
+  <ItemGroup>
+    <!-- Package the generator in the analyzer directory of the nuget package -->
+    <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
+
+    <!-- Package the props file -->
+    <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="false" />
+  </ItemGroup>
+
+  <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
+    <PropertyGroup>
+      <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
+      <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
+    </PropertyGroup>
+    <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
+  </Target>
+</Project>

+ 7 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props

@@ -0,0 +1,7 @@
+<Project>
+  <ItemGroup>
+    <!-- $(GodotProjectDir) is defined by Godot.NET.Sdk -->
+    <CompilerVisibleProperty Include="GodotProjectDir" />
+    <CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" />
+  </ItemGroup>
+</Project>

+ 9 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs

@@ -0,0 +1,9 @@
+namespace Godot.SourceGenerators
+{
+    public static class GodotClasses
+    {
+        public const string Object = "Godot.Object";
+        public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute";
+        public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
+    }
+}

+ 156 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs

@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators
+{
+    [Generator]
+    public class ScriptPathAttributeGenerator : ISourceGenerator
+    {
+        public void Execute(GeneratorExecutionContext context)
+        {
+            if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle)
+                && toggle == "disabled")
+            {
+                return;
+            }
+
+            // NOTE: IsNullOrEmpty doesn't work well with nullable checks
+            // ReSharper disable once ReplaceWithStringIsNullOrEmpty
+            if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir)
+                || godotProjectDir!.Length == 0)
+            {
+                throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty.");
+            }
+
+            var godotClasses = context.Compilation.SyntaxTrees
+                .SelectMany(tree =>
+                    tree.GetRoot().DescendantNodes()
+                        .OfType<ClassDeclarationSyntax>()
+                        // Ignore inner classes
+                        .Where(cds => !(cds.Parent is ClassDeclarationSyntax))
+                        .SelectGodotScriptClasses(context.Compilation)
+                        // Report and skip non-partial classes
+                        .Where(x =>
+                        {
+                            if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute())
+                                return true;
+                            Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
+                            return false;
+                        })
+                )
+                // Ignore classes whose name is not the same as the file name
+                .Where(x => Path.GetFileNameWithoutExtension(x.cds.SyntaxTree.FilePath) == x.symbol.Name)
+                .GroupBy(x => x.symbol)
+                .ToDictionary(g => g.Key, g => g.Select(x => x.cds));
+
+            foreach (var godotClass in godotClasses)
+            {
+                VisitGodotScriptClass(context, godotProjectDir,
+                    symbol: godotClass.Key,
+                    classDeclarations: godotClass.Value);
+            }
+
+            if (godotClasses.Count <= 0)
+                return;
+
+            AddScriptTypesAssemblyAttr(context, godotClasses);
+        }
+
+        private static void VisitGodotScriptClass(
+            GeneratorExecutionContext context,
+            string godotProjectDir,
+            INamedTypeSymbol symbol,
+            IEnumerable<ClassDeclarationSyntax> classDeclarations
+        )
+        {
+            var attributesBuilder = new StringBuilder();
+
+            // Remember syntax trees for which we already added an attribute, to prevent unnecessary duplicates.
+            var attributedTrees = new List<SyntaxTree>();
+
+            foreach (var cds in classDeclarations)
+            {
+                if (attributedTrees.Contains(cds.SyntaxTree))
+                    continue;
+
+                attributedTrees.Add(cds.SyntaxTree);
+
+                if (attributesBuilder.Length != 0)
+                    attributesBuilder.Append("\n    ");
+
+                attributesBuilder.Append(@"[ScriptPathAttribute(""res://");
+                attributesBuilder.Append(RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir));
+                attributesBuilder.Append(@""")]");
+            }
+
+            string classNs = symbol.ContainingNamespace.Name;
+            string className = symbol.Name;
+
+            var source = $@"using Godot;
+namespace {classNs}
+{{
+    {attributesBuilder}
+    partial class {className}
+    {{
+    }}
+}}
+";
+            context.AddSource(classNs + "." + className + "_ScriptPath_Generated",
+                SourceText.From(source, Encoding.UTF8));
+        }
+
+        private static void AddScriptTypesAssemblyAttr(GeneratorExecutionContext context,
+            Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses)
+        {
+            var sourceBuilder = new StringBuilder();
+
+            sourceBuilder.Append("[assembly:");
+            sourceBuilder.Append(GodotClasses.AssemblyHasScriptsAttr);
+            sourceBuilder.Append("(new System.Type[] {");
+
+            bool first = true;
+
+            foreach (var godotClass in godotClasses)
+            {
+                var qualifiedName = godotClass.Key.ToDisplayString(
+                    NullableFlowState.NotNull, SymbolDisplayFormat.FullyQualifiedFormat);
+                if (!first)
+                    sourceBuilder.Append(", ");
+                first = false;
+                sourceBuilder.Append("typeof(");
+                sourceBuilder.Append(qualifiedName);
+                sourceBuilder.Append(")");
+            }
+
+            sourceBuilder.Append("})]\n");
+
+            context.AddSource("AssemblyScriptTypes_Generated",
+                SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
+        }
+
+        public void Initialize(GeneratorInitializationContext context)
+        {
+        }
+
+        private static string RelativeToDir(string path, string dir)
+        {
+            // Make sure the directory ends with a path separator
+            dir = Path.Combine(dir, " ").TrimEnd();
+
+            if (Path.DirectorySeparatorChar == '\\')
+                dir = dir.Replace("/", "\\") + "\\";
+
+            var fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute);
+            var relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute);
+
+            // MakeRelativeUri converts spaces to %20, hence why we need UnescapeDataString
+            return Uri.UnescapeDataString(relRoot.MakeRelativeUri(fullPath).ToString());
+        }
+    }
+}

+ 0 - 47
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs

@@ -1,11 +1,5 @@
 using System;
-using GodotTools.Core;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
 using Microsoft.Build.Construction;
-using Microsoft.Build.Globbing;
 
 namespace GodotTools.ProjectEditor
 {
@@ -31,47 +25,6 @@ namespace GodotTools.ProjectEditor
             return root != null ? new MSBuildProject(root) : null;
         }
 
-        private static List<string> GetAllFilesRecursive(string rootDirectory, string mask)
-        {
-            string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories);
-
-            // We want relative paths
-            for (int i = 0; i < files.Length; i++)
-            {
-                files[i] = files[i].RelativeToPath(rootDirectory);
-            }
-
-            return new List<string>(files);
-        }
-
-        // NOTE: Assumes auto-including items. Only used by the scripts metadata generator, which will be replaced with source generators in the future.
-        public static IEnumerable<string> GetIncludeFiles(string projectPath, string itemType)
-        {
-            var excluded = new List<string>();
-            var includedFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
-
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            foreach (var item in root.Items)
-            {
-                if (string.IsNullOrEmpty(item.Condition))
-                    continue;
-
-                if (item.ItemType != itemType)
-                    continue;
-
-                string normalizedRemove = item.Remove.NormalizePath();
-
-                var glob = MSBuildGlob.Parse(normalizedRemove);
-                excluded.AddRange(includedFiles.Where(includedFile => glob.IsMatch(includedFile)));
-            }
-
-            includedFiles.RemoveAll(f => excluded.Contains(f));
-
-            return includedFiles;
-        }
-
         public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
         {
             var origRoot = project.Root;

+ 3 - 3
modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets

@@ -3,7 +3,6 @@
 
   <Target Name="SetPropertiesForGenerateGodotNupkgsVersions">
     <PropertyGroup>
-      <GodotNETSdkPackageVersionFile>$(SolutionDir)..\Godot.NET.Sdk\Godot.NET.Sdk\Godot.NET.Sdk_PackageVersion.txt</GodotNETSdkPackageVersionFile>
       <GeneratedGodotNupkgsVersionsFile>$(IntermediateOutputPath)GodotNupkgsVersions.g.cs</GeneratedGodotNupkgsVersionsFile>
     </PropertyGroup>
   </Target>
@@ -18,13 +17,14 @@
   </Target>
   <Target Name="_GenerateGodotNupkgsVersionsFile"
           DependsOnTargets="SetPropertiesForGenerateGodotNupkgsVersions"
-          Inputs="$(MSBuildProjectFile);@(GodotNETSdkPackageVersionFile)"
+          Inputs="$(MSBuildProjectFile);$(MSBuildThisFileDirectory);$(MSBuildProjectFile)\..\..\..\SdkPackageVersions.props"
           Outputs="$(GeneratedGodotNupkgsVersionsFile)">
     <PropertyGroup>
       <GenerateGodotNupkgsVersionsCode><![CDATA[
 namespace $(RootNamespace) {
     public class GeneratedGodotNupkgsVersions {
-        public const string GodotNETSdk = "$([System.IO.File]::ReadAllText('$(GodotNETSdkPackageVersionFile)').Trim())"%3b
+        public const string GodotNETSdk = "$(PackageVersion_Godot_NET_Sdk)"%3b
+        public const string GodotSourceGenerators = "$(PackageVersion_Godot_SourceGenerators)"%3b
     }
 }
 ]]></GenerateGodotNupkgsVersionsCode>

+ 0 - 31
modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs

@@ -218,43 +218,12 @@ namespace GodotTools.Build
                 Godot.GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
             }
 
-            GenerateEditorScriptMetadata();
-
             if (GodotSharpEditor.Instance.SkipBuildBeforePlaying)
                 return true; // Requested play from an external editor/IDE which already built the project
 
             return BuildProjectBlocking("Debug");
         }
 
-        // NOTE: This will be replaced with C# source generators in 4.0
-        public static void GenerateEditorScriptMetadata()
-        {
-            string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
-            string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
-
-            CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
-
-            if (!File.Exists(editorScriptsMetadataPath))
-                return;
-
-            try
-            {
-                File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
-            }
-            catch (IOException e)
-            {
-                throw new IOException("Failed to copy scripts metadata file.", innerException: e);
-            }
-        }
-
-        // NOTE: This will be replaced with C# source generators in 4.0
-        public static string GenerateExportedGameScriptMetadata(bool isDebug)
-        {
-            string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}");
-            CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
-            return scriptsMetadataPath;
-        }
-
         public static void Initialize()
         {
             // Build tool settings

+ 0 - 4
modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs

@@ -43,8 +43,6 @@ namespace GodotTools.Build
                 GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
             }
 
-            BuildManager.GenerateEditorScriptMetadata();
-
             if (!BuildManager.BuildProjectBlocking("Debug"))
                 return; // Build failed
 
@@ -74,8 +72,6 @@ namespace GodotTools.Build
                 GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
             }
 
-            BuildManager.GenerateEditorScriptMetadata();
-
             if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Rebuild"}))
                 return; // Build failed
 

+ 2 - 1
modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs

@@ -290,7 +290,8 @@ namespace GodotTools.Build
 
         private static readonly (string packageId, string packageVersion)[] PackagesToAdd =
         {
-            ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk)
+            ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk),
+            ("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators),
         };
     }
 }

+ 0 - 86
modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs

@@ -1,11 +1,6 @@
 using Godot;
 using System;
-using System.Linq;
-using Godot.Collections;
-using GodotTools.Internals;
 using GodotTools.ProjectEditor;
-using File = GodotTools.Utils.File;
-using Directory = GodotTools.Utils.Directory;
 
 namespace GodotTools
 {
@@ -23,86 +18,5 @@ namespace GodotTools
                 return string.Empty;
             }
         }
-
-        private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
-
-        private static ulong ConvertToTimestamp(this DateTime value)
-        {
-            TimeSpan elapsedTime = value - Epoch;
-            return (ulong)elapsedTime.TotalSeconds;
-        }
-
-        private static bool TryParseFileMetadata(string includeFile, ulong modifiedTime, out Dictionary fileMetadata)
-        {
-            fileMetadata = null;
-
-            var parseError = ScriptClassParser.ParseFile(includeFile, out var classes, out string errorStr);
-
-            if (parseError != Error.Ok)
-            {
-                GD.PushError($"Failed to determine namespace and class for script: {includeFile}. Parse error: {errorStr ?? parseError.ToString()}");
-                return false;
-            }
-
-            string searchName = System.IO.Path.GetFileNameWithoutExtension(includeFile);
-
-            var firstMatch = classes.FirstOrDefault(classDecl =>
-                    classDecl.BaseCount != 0 && // If it doesn't inherit anything, it can't be a Godot.Object.
-                    classDecl.SearchName == searchName // Filter by the name we're looking for
-            );
-
-            if (firstMatch == null)
-                return false; // Not found
-
-            fileMetadata = new Dictionary
-            {
-                ["modified_time"] = $"{modifiedTime}",
-                ["class"] = new Dictionary
-                {
-                    ["namespace"] = firstMatch.Namespace,
-                    ["class_name"] = firstMatch.Name,
-                    ["nested"] = firstMatch.Nested
-                }
-            };
-
-            return true;
-        }
-
-        public static void GenerateScriptsMetadata(string projectPath, string outputPath)
-        {
-            var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate();
-
-            bool IsUpToDate(string includeFile, ulong modifiedTime)
-            {
-                return metadataDict.TryGetValue(includeFile, out var oldFileVar) &&
-                       ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string,
-                           out ulong storedModifiedTime) && storedModifiedTime == modifiedTime;
-            }
-
-            var outdatedFiles = ProjectUtils.GetIncludeFiles(projectPath, "Compile")
-                .Select(path => ("res://" + path).SimplifyGodotPath())
-                .ToDictionary(path => path, path => File.GetLastWriteTime(path).ConvertToTimestamp())
-                .Where(pair => !IsUpToDate(includeFile: pair.Key, modifiedTime: pair.Value))
-                .ToArray();
-
-            foreach (var pair in outdatedFiles)
-            {
-                metadataDict.Remove(pair.Key);
-
-                string includeFile = pair.Key;
-
-                if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata))
-                    metadataDict[includeFile] = fileMetadata;
-            }
-
-            string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict);
-
-            string baseDir = outputPath.GetBaseDir();
-
-            if (!Directory.Exists(baseDir))
-                Directory.CreateDirectory(baseDir);
-
-            File.WriteAllText(outputPath, json);
-        }
     }
 }

+ 0 - 3
modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs

@@ -157,9 +157,6 @@ namespace GodotTools.Export
 
             string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";
 
-            string scriptsMetadataPath = BuildManager.GenerateExportedGameScriptMetadata(isDebug);
-            AddFile(scriptsMetadataPath, scriptsMetadataPath);
-
             if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform))
                 throw new Exception("Failed to build project");
 

+ 0 - 8
modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs

@@ -1,7 +1,5 @@
-using System;
 using System.Runtime.CompilerServices;
 using Godot;
-using Godot.Collections;
 using GodotTools.IdeMessaging.Requests;
 
 namespace GodotTools.Internals
@@ -42,9 +40,6 @@ namespace GodotTools.Internals
 
         public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen();
 
-        public static Dictionary<string, object> GetScriptsMetadataOrNothing() =>
-            internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>));
-
         public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot();
 
         public static void EditorRunPlay() => internal_EditorRunPlay();
@@ -100,9 +95,6 @@ namespace GodotTools.Internals
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void internal_EditorNodeShowScriptScreen();
 
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType);
-
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern string internal_MonoWindowsInstallRoot();
 

+ 0 - 61
modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs

@@ -1,61 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using Godot;
-using Godot.Collections;
-
-namespace GodotTools.Internals
-{
-    public static class ScriptClassParser
-    {
-        public class ClassDecl
-        {
-            public string Name { get; }
-            public string Namespace { get; }
-            public bool Nested { get; }
-            public long BaseCount { get; }
-
-            public string SearchName => Nested ?
-                Name.Substring(Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
-                Name;
-
-            public ClassDecl(string name, string @namespace, bool nested, long baseCount)
-            {
-                Name = name;
-                Namespace = @namespace;
-                Nested = nested;
-                BaseCount = baseCount;
-            }
-        }
-
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes, out string errorStr);
-
-        public static Error ParseFile(string filePath, out IEnumerable<ClassDecl> classes, out string errorStr)
-        {
-            var classesArray = new Array<Dictionary>();
-            var error = internal_ParseFile(filePath, classesArray, out errorStr);
-            if (error != Error.Ok)
-            {
-                classes = null;
-                return error;
-            }
-
-            var classesList = new List<ClassDecl>();
-
-            foreach (var classDeclDict in classesArray)
-            {
-                classesList.Add(new ClassDecl(
-                    (string)classDeclDict["name"],
-                    (string)classDeclDict["namespace"],
-                    (bool)classDeclDict["nested"],
-                    (long)classDeclDict["base_count"]
-                ));
-            }
-
-            classes = classesList;
-
-            return Error.Ok;
-        }
-    }
-}

+ 0 - 47
modules/mono/editor/editor_internal_calls.cpp

@@ -49,7 +49,6 @@
 #include "../utils/osx_utils.h"
 #include "code_completion.h"
 #include "godotsharp_export.h"
-#include "script_class_parser.h"
 
 MonoString *godot_icall_GodotSharpDirs_ResDataDir() {
 	return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir());
@@ -172,36 +171,6 @@ MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_st
 	return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh);
 }
 
-int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes, MonoString **r_error_str) {
-	*r_error_str = nullptr;
-
-	String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath);
-
-	ScriptClassParser scp;
-	Error err = scp.parse_file(filepath);
-	if (err == OK) {
-		Array classes = GDMonoMarshal::mono_object_to_variant(p_classes);
-		const Vector<ScriptClassParser::ClassDecl> &class_decls = scp.get_classes();
-
-		for (int i = 0; i < class_decls.size(); i++) {
-			const ScriptClassParser::ClassDecl &classDecl = class_decls[i];
-
-			Dictionary classDeclDict;
-			classDeclDict["name"] = classDecl.name;
-			classDeclDict["namespace"] = classDecl.namespace_;
-			classDeclDict["nested"] = classDecl.nested;
-			classDeclDict["base_count"] = classDecl.base.size();
-			classes.push_back(classDeclDict);
-		}
-	} else {
-		String error_str = scp.get_error();
-		if (!error_str.is_empty()) {
-			*r_error_str = GDMonoMarshal::mono_string_from_godot(error_str);
-		}
-	}
-	return err;
-}
-
 uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_assemblies,
 		MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_assembly_dependencies) {
 	Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_assemblies);
@@ -289,18 +258,6 @@ void godot_icall_Internal_EditorNodeShowScriptScreen() {
 	EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT);
 }
 
-MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) {
-	Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing();
-
-	MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype);
-
-	int type_encoding = mono_type_get_type(dict_type);
-	MonoClass *type_class_raw = mono_class_from_mono_type(dict_type);
-	GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw);
-
-	return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class));
-}
-
 MonoString *godot_icall_Internal_MonoWindowsInstallRoot() {
 #ifdef WINDOWS_ENABLED
 	String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir;
@@ -395,9 +352,6 @@ void register_editor_internal_calls() {
 	GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", godot_icall_EditorProgress_Dispose);
 	GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", godot_icall_EditorProgress_Step);
 
-	// ScriptClassParser
-	GDMonoUtils::add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", godot_icall_ScriptClassParser_ParseFile);
-
 	// ExportPlugin
 	GDMonoUtils::add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", godot_icall_ExportPlugin_GetExportedAssemblyDependencies);
 
@@ -416,7 +370,6 @@ void register_editor_internal_calls() {
 	GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", godot_icall_Internal_EditorDebuggerNodeReloadScripts);
 	GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", godot_icall_Internal_ScriptEditorEdit);
 	GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", godot_icall_Internal_EditorNodeShowScriptScreen);
-	GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", godot_icall_Internal_GetScriptsMetadataOrNothing);
 	GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", godot_icall_Internal_MonoWindowsInstallRoot);
 	GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", godot_icall_Internal_EditorRunPlay);
 	GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", godot_icall_Internal_EditorRunStop);

+ 0 - 753
modules/mono/editor/script_class_parser.cpp

@@ -1,753 +0,0 @@
-/*************************************************************************/
-/*  script_class_parser.cpp                                              */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-
-#include "script_class_parser.h"
-
-#include "core/os/os.h"
-#include "core/templates/map.h"
-
-#include "../utils/string_utils.h"
-
-const char *ScriptClassParser::token_names[ScriptClassParser::TK_MAX] = {
-	"[",
-	"]",
-	"{",
-	"}",
-	".",
-	":",
-	",",
-	"Symbol",
-	"Identifier",
-	"String",
-	"Number",
-	"<",
-	">",
-	"EOF",
-	"Error"
-};
-
-String ScriptClassParser::get_token_name(ScriptClassParser::Token p_token) {
-	ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>");
-	return token_names[p_token];
-}
-
-ScriptClassParser::Token ScriptClassParser::get_token() {
-	while (true) {
-		switch (code[idx]) {
-			case '\n': {
-				line++;
-				idx++;
-				break;
-			};
-			case 0: {
-				return TK_EOF;
-			} break;
-			case '{': {
-				idx++;
-				return TK_CURLY_BRACKET_OPEN;
-			};
-			case '}': {
-				idx++;
-				return TK_CURLY_BRACKET_CLOSE;
-			};
-			case '[': {
-				idx++;
-				return TK_BRACKET_OPEN;
-			};
-			case ']': {
-				idx++;
-				return TK_BRACKET_CLOSE;
-			};
-			case '<': {
-				idx++;
-				return TK_OP_LESS;
-			};
-			case '>': {
-				idx++;
-				return TK_OP_GREATER;
-			};
-			case ':': {
-				idx++;
-				return TK_COLON;
-			};
-			case ',': {
-				idx++;
-				return TK_COMMA;
-			};
-			case '.': {
-				idx++;
-				return TK_PERIOD;
-			};
-			case '#': {
-				//compiler directive
-				while (code[idx] != '\n' && code[idx] != 0) {
-					idx++;
-				}
-				continue;
-			} break;
-			case '/': {
-				switch (code[idx + 1]) {
-					case '*': { // block comment
-						idx += 2;
-						while (true) {
-							if (code[idx] == 0) {
-								error_str = "Unterminated comment";
-								error = true;
-								return TK_ERROR;
-							} else if (code[idx] == '*' && code[idx + 1] == '/') {
-								idx += 2;
-								break;
-							} else if (code[idx] == '\n') {
-								line++;
-							}
-
-							idx++;
-						}
-
-					} break;
-					case '/': { // line comment skip
-						while (code[idx] != '\n' && code[idx] != 0) {
-							idx++;
-						}
-
-					} break;
-					default: {
-						value = "/";
-						idx++;
-						return TK_SYMBOL;
-					}
-				}
-
-				continue; // a comment
-			} break;
-			case '\'':
-			case '"': {
-				bool verbatim = idx != 0 && code[idx - 1] == '@';
-
-				char32_t begin_str = code[idx];
-				idx++;
-				String tk_string = String();
-				while (true) {
-					if (code[idx] == 0) {
-						error_str = "Unterminated String";
-						error = true;
-						return TK_ERROR;
-					} else if (code[idx] == begin_str) {
-						if (verbatim && code[idx + 1] == '"') { // '""' is verbatim string's '\"'
-							idx += 2; // skip next '"' as well
-							continue;
-						}
-
-						idx += 1;
-						break;
-					} else if (code[idx] == '\\' && !verbatim) {
-						//escaped characters...
-						idx++;
-						char32_t next = code[idx];
-						if (next == 0) {
-							error_str = "Unterminated String";
-							error = true;
-							return TK_ERROR;
-						}
-						char32_t res = 0;
-
-						switch (next) {
-							case 'b':
-								res = 8;
-								break;
-							case 't':
-								res = 9;
-								break;
-							case 'n':
-								res = 10;
-								break;
-							case 'f':
-								res = 12;
-								break;
-							case 'r':
-								res = 13;
-								break;
-							case '\"':
-								res = '\"';
-								break;
-							case '\\':
-								res = '\\';
-								break;
-							default: {
-								res = next;
-							} break;
-						}
-
-						tk_string += res;
-
-					} else {
-						if (code[idx] == '\n') {
-							line++;
-						}
-						tk_string += code[idx];
-					}
-					idx++;
-				}
-
-				value = tk_string;
-
-				return TK_STRING;
-			} break;
-			default: {
-				if (code[idx] <= 32) {
-					idx++;
-					break;
-				}
-
-				if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 63) || (code[idx] >= 91 && code[idx] <= 94) || code[idx] == 96 || (code[idx] >= 123 && code[idx] <= 127)) {
-					value = String::chr(code[idx]);
-					idx++;
-					return TK_SYMBOL;
-				}
-
-				if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) {
-					//a number
-					const char32_t *rptr;
-					double number = String::to_float(&code[idx], &rptr);
-					idx += (rptr - &code[idx]);
-					value = number;
-					return TK_NUMBER;
-
-				} else if ((code[idx] == '@' && code[idx + 1] != '"') || code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) {
-					String id;
-
-					id += code[idx];
-					idx++;
-
-					while (code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || (code[idx] >= '0' && code[idx] <= '9') || code[idx] > 127) {
-						id += code[idx];
-						idx++;
-					}
-
-					value = id;
-					return TK_IDENTIFIER;
-				} else if (code[idx] == '@' && code[idx + 1] == '"') {
-					// begin of verbatim string
-					idx++;
-				} else {
-					error_str = "Unexpected character.";
-					error = true;
-					return TK_ERROR;
-				}
-			}
-		}
-	}
-}
-
-Error ScriptClassParser::_skip_generic_type_params() {
-	Token tk;
-
-	while (true) {
-		tk = get_token();
-
-		if (tk == TK_IDENTIFIER) {
-			tk = get_token();
-			// Type specifications can end with "?" to denote nullable types, such as IList<int?>
-			if (tk == TK_SYMBOL) {
-				tk = get_token();
-				if (value.operator String() != "?") {
-					error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found unexpected symbol '" + value + "'";
-					error = true;
-					return ERR_PARSE_ERROR;
-				}
-				if (tk != TK_OP_GREATER && tk != TK_COMMA) {
-					error_str = "Nullable type symbol '?' is only allowed after an identifier, but found " + get_token_name(tk) + " next.";
-					error = true;
-					return ERR_PARSE_ERROR;
-				}
-			}
-
-			if (tk == TK_PERIOD) {
-				while (true) {
-					tk = get_token();
-
-					if (tk != TK_IDENTIFIER) {
-						error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
-						error = true;
-						return ERR_PARSE_ERROR;
-					}
-
-					tk = get_token();
-
-					if (tk != TK_PERIOD) {
-						break;
-					}
-				}
-			}
-
-			if (tk == TK_OP_LESS) {
-				Error err = _skip_generic_type_params();
-				if (err) {
-					return err;
-				}
-				tk = get_token();
-			}
-
-			if (tk == TK_OP_GREATER) {
-				return OK;
-			} else if (tk != TK_COMMA) {
-				error_str = "Unexpected token: " + get_token_name(tk);
-				error = true;
-				return ERR_PARSE_ERROR;
-			}
-		} else if (tk == TK_OP_LESS) {
-			error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found " + get_token_name(TK_OP_LESS);
-			error = true;
-			return ERR_PARSE_ERROR;
-		} else if (tk == TK_OP_GREATER) {
-			return OK;
-		} else {
-			error_str = "Unexpected token: " + get_token_name(tk);
-			error = true;
-			return ERR_PARSE_ERROR;
-		}
-	}
-}
-
-Error ScriptClassParser::_parse_type_full_name(String &r_full_name) {
-	Token tk = get_token();
-
-	if (tk != TK_IDENTIFIER) {
-		error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
-		error = true;
-		return ERR_PARSE_ERROR;
-	}
-
-	r_full_name += String(value);
-
-	if (code[idx] == '<') {
-		idx++;
-
-		// We don't mind if the base is generic, but we skip it any ways since this information is not needed
-		Error err = _skip_generic_type_params();
-		if (err) {
-			return err;
-		}
-	}
-
-	if (code[idx] != '.') { // We only want to take the next token if it's a period
-		return OK;
-	}
-
-	tk = get_token();
-
-	CRASH_COND(tk != TK_PERIOD); // Assertion
-
-	r_full_name += ".";
-
-	return _parse_type_full_name(r_full_name);
-}
-
-Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) {
-	String name;
-
-	Error err = _parse_type_full_name(name);
-	if (err) {
-		return err;
-	}
-
-	Token tk = get_token();
-
-	if (tk == TK_COMMA) {
-		err = _parse_class_base(r_base);
-		if (err) {
-			return err;
-		}
-	} else if (tk == TK_IDENTIFIER && String(value) == "where") {
-		err = _parse_type_constraints();
-		if (err) {
-			return err;
-		}
-
-		// An open curly bracket was parsed by _parse_type_constraints, so we can exit
-	} else if (tk == TK_CURLY_BRACKET_OPEN) {
-		// we are finished when we hit the open curly bracket
-	} else {
-		error_str = "Unexpected token: " + get_token_name(tk);
-		error = true;
-		return ERR_PARSE_ERROR;
-	}
-
-	r_base.push_back(name);
-
-	return OK;
-}
-
-Error ScriptClassParser::_parse_type_constraints() {
-	Token tk = get_token();
-	if (tk != TK_IDENTIFIER) {
-		error_str = "Unexpected token: " + get_token_name(tk);
-		error = true;
-		return ERR_PARSE_ERROR;
-	}
-
-	tk = get_token();
-	if (tk != TK_COLON) {
-		error_str = "Unexpected token: " + get_token_name(tk);
-		error = true;
-		return ERR_PARSE_ERROR;
-	}
-
-	while (true) {
-		tk = get_token();
-		if (tk == TK_IDENTIFIER) {
-			if (String(value) == "where") {
-				return _parse_type_constraints();
-			}
-
-			tk = get_token();
-			if (tk == TK_PERIOD) {
-				while (true) {
-					tk = get_token();
-
-					if (tk != TK_IDENTIFIER) {
-						error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
-						error = true;
-						return ERR_PARSE_ERROR;
-					}
-
-					tk = get_token();
-
-					if (tk != TK_PERIOD) {
-						break;
-					}
-				}
-			}
-		}
-
-		if (tk == TK_COMMA) {
-			continue;
-		} else if (tk == TK_IDENTIFIER && String(value) == "where") {
-			return _parse_type_constraints();
-		} else if (tk == TK_SYMBOL && String(value) == "(") {
-			tk = get_token();
-			if (tk != TK_SYMBOL || String(value) != ")") {
-				error_str = "Unexpected token: " + get_token_name(tk);
-				error = true;
-				return ERR_PARSE_ERROR;
-			}
-		} else if (tk == TK_OP_LESS) {
-			Error err = _skip_generic_type_params();
-			if (err) {
-				return err;
-			}
-		} else if (tk == TK_CURLY_BRACKET_OPEN) {
-			return OK;
-		} else {
-			error_str = "Unexpected token: " + get_token_name(tk);
-			error = true;
-			return ERR_PARSE_ERROR;
-		}
-	}
-}
-
-Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) {
-	Token tk = get_token();
-
-	if (tk == TK_IDENTIFIER) {
-		r_name += String(value);
-	} else {
-		error_str = "Unexpected token: " + get_token_name(tk);
-		error = true;
-		return ERR_PARSE_ERROR;
-	}
-
-	tk = get_token();
-
-	if (tk == TK_PERIOD) {
-		r_name += ".";
-		return _parse_namespace_name(r_name, r_curly_stack);
-	} else if (tk == TK_CURLY_BRACKET_OPEN) {
-		r_curly_stack++;
-		return OK;
-	} else {
-		error_str = "Unexpected token: " + get_token_name(tk);
-		error = true;
-		return ERR_PARSE_ERROR;
-	}
-}
-
-Error ScriptClassParser::parse(const String &p_code) {
-	code = p_code;
-	idx = 0;
-	line = 0;
-	error_str = String();
-	error = false;
-	value = Variant();
-	classes.clear();
-
-	Token tk = get_token();
-
-	Map<int, NameDecl> name_stack;
-	int curly_stack = 0;
-	int type_curly_stack = 0;
-
-	while (!error && tk != TK_EOF) {
-		String identifier = value;
-		if (tk == TK_IDENTIFIER && (identifier == "class" || identifier == "struct")) {
-			bool is_class = identifier == "class";
-
-			tk = get_token();
-
-			if (tk == TK_IDENTIFIER) {
-				String name = value;
-				int at_level = curly_stack;
-
-				ClassDecl class_decl;
-
-				for (Map<int, NameDecl>::Element *E = name_stack.front(); E; E = E->next()) {
-					const NameDecl &name_decl = E->value();
-
-					if (name_decl.type == NameDecl::NAMESPACE_DECL) {
-						if (E != name_stack.front()) {
-							class_decl.namespace_ += ".";
-						}
-						class_decl.namespace_ += name_decl.name;
-					} else {
-						class_decl.name += name_decl.name + ".";
-					}
-				}
-
-				class_decl.name += name;
-				class_decl.nested = type_curly_stack > 0;
-
-				bool generic = false;
-
-				while (true) {
-					tk = get_token();
-
-					if (tk == TK_COLON) {
-						Error err = _parse_class_base(class_decl.base);
-						if (err) {
-							return err;
-						}
-
-						curly_stack++;
-						type_curly_stack++;
-
-						break;
-					} else if (tk == TK_CURLY_BRACKET_OPEN) {
-						curly_stack++;
-						type_curly_stack++;
-						break;
-					} else if (tk == TK_OP_LESS && !generic) {
-						generic = true;
-
-						Error err = _skip_generic_type_params();
-						if (err) {
-							return err;
-						}
-					} else if (tk == TK_IDENTIFIER && String(value) == "where") {
-						Error err = _parse_type_constraints();
-						if (err) {
-							return err;
-						}
-
-						// An open curly bracket was parsed by _parse_type_constraints, so we can exit
-						curly_stack++;
-						type_curly_stack++;
-						break;
-					} else {
-						error_str = "Unexpected token: " + get_token_name(tk);
-						error = true;
-						return ERR_PARSE_ERROR;
-					}
-				}
-
-				NameDecl name_decl;
-				name_decl.name = name;
-				name_decl.type = is_class ? NameDecl::CLASS_DECL : NameDecl::STRUCT_DECL;
-				name_stack[at_level] = name_decl;
-
-				if (is_class) {
-					if (!generic) { // no generics, thanks
-						classes.push_back(class_decl);
-					} else if (OS::get_singleton()->is_stdout_verbose()) {
-						String full_name = class_decl.namespace_;
-						if (full_name.length()) {
-							full_name += ".";
-						}
-						full_name += class_decl.name;
-						OS::get_singleton()->print("Ignoring generic class declaration: %s\n", full_name.utf8().get_data());
-					}
-				}
-			}
-		} else if (tk == TK_IDENTIFIER && identifier == "namespace") {
-			if (type_curly_stack > 0) {
-				error_str = "Found namespace nested inside type.";
-				error = true;
-				return ERR_PARSE_ERROR;
-			}
-
-			String name;
-			int at_level = curly_stack;
-
-			Error err = _parse_namespace_name(name, curly_stack);
-			if (err) {
-				return err;
-			}
-
-			NameDecl name_decl;
-			name_decl.name = name;
-			name_decl.type = NameDecl::NAMESPACE_DECL;
-			name_stack[at_level] = name_decl;
-		} else if (tk == TK_CURLY_BRACKET_OPEN) {
-			curly_stack++;
-		} else if (tk == TK_CURLY_BRACKET_CLOSE) {
-			curly_stack--;
-			if (name_stack.has(curly_stack)) {
-				if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL) {
-					type_curly_stack--;
-				}
-				name_stack.erase(curly_stack);
-			}
-		}
-
-		tk = get_token();
-	}
-
-	if (!error && tk == TK_EOF && curly_stack > 0) {
-		error_str = "Reached EOF with missing close curly brackets.";
-		error = true;
-	}
-
-	if (error) {
-		return ERR_PARSE_ERROR;
-	}
-
-	return OK;
-}
-
-static String get_preprocessor_directive(const String &p_line, int p_from) {
-	CRASH_COND(p_line[p_from] != '#');
-	p_from++;
-	int i = p_from;
-	while (i < p_line.length() && (p_line[i] == '_' || (p_line[i] >= 'A' && p_line[i] <= 'Z') ||
-										  (p_line[i] >= 'a' && p_line[i] <= 'z') || p_line[i] > 127)) {
-		i++;
-	}
-	return p_line.substr(p_from, i - p_from);
-}
-
-static void run_dummy_preprocessor(String &r_source, const String &p_filepath) {
-	Vector<String> lines = r_source.split("\n", /* p_allow_empty: */ true);
-
-	bool *include_lines = memnew_arr(bool, lines.size());
-
-	int if_level = -1;
-	Vector<bool> is_branch_being_compiled;
-
-	for (int i = 0; i < lines.size(); i++) {
-		const String &line = lines[i];
-
-		const int line_len = line.length();
-
-		int j;
-		for (j = 0; j < line_len; j++) {
-			if (line[j] != ' ' && line[j] != '\t') {
-				if (line[j] == '#') {
-					// First non-whitespace char of the line is '#'
-					include_lines[i] = false;
-
-					String directive = get_preprocessor_directive(line, j);
-
-					if (directive == "if") {
-						if_level++;
-						is_branch_being_compiled.push_back(if_level == 0 || is_branch_being_compiled[if_level - 1]);
-					} else if (directive == "elif") {
-						ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#elif' directive. File: '" + p_filepath + "'.");
-						is_branch_being_compiled.write[if_level] = false;
-					} else if (directive == "else") {
-						ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#else' directive. File: '" + p_filepath + "'.");
-						is_branch_being_compiled.write[if_level] = false;
-					} else if (directive == "endif") {
-						ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#endif' directive. File: '" + p_filepath + "'.");
-						is_branch_being_compiled.remove(if_level);
-						if_level--;
-					}
-
-					break;
-				} else {
-					// First non-whitespace char of the line is not '#'
-					include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level];
-					break;
-				}
-			}
-		}
-
-		if (j == line_len) {
-			// Loop ended without finding a non-whitespace character.
-			// Either the line was empty or it only contained whitespaces.
-			include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level];
-		}
-	}
-
-	r_source.clear();
-
-	// Custom join ignoring lines removed by the preprocessor
-	for (int i = 0; i < lines.size(); i++) {
-		if (i > 0 && include_lines[i - 1]) {
-			r_source += '\n';
-		}
-
-		if (include_lines[i]) {
-			r_source += lines[i];
-		}
-	}
-}
-
-Error ScriptClassParser::parse_file(const String &p_filepath) {
-	String source;
-
-	Error ferr = read_all_file_utf8(p_filepath, source);
-
-	ERR_FAIL_COND_V_MSG(ferr != OK, ferr,
-			ferr == ERR_INVALID_DATA ?
-					  "File '" + p_filepath + "' contains invalid unicode (UTF-8), so it was not loaded."
-											" Please ensure that scripts are saved in valid UTF-8 unicode." :
-					  "Failed to read file: '" + p_filepath + "'.");
-
-	run_dummy_preprocessor(source, p_filepath);
-
-	return parse(source);
-}
-
-String ScriptClassParser::get_error() {
-	return error_str;
-}
-
-Vector<ScriptClassParser::ClassDecl> ScriptClassParser::get_classes() {
-	return classes;
-}

+ 0 - 108
modules/mono/editor/script_class_parser.h

@@ -1,108 +0,0 @@
-/*************************************************************************/
-/*  script_class_parser.h                                                */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-
-#ifndef SCRIPT_CLASS_PARSER_H
-#define SCRIPT_CLASS_PARSER_H
-
-#include "core/string/ustring.h"
-#include "core/templates/vector.h"
-#include "core/variant/variant.h"
-
-class ScriptClassParser {
-public:
-	struct NameDecl {
-		enum Type {
-			NAMESPACE_DECL,
-			CLASS_DECL,
-			STRUCT_DECL
-		};
-
-		String name;
-		Type type = NAMESPACE_DECL;
-	};
-
-	struct ClassDecl {
-		String name;
-		String namespace_;
-		Vector<String> base;
-		bool nested = false;
-	};
-
-private:
-	String code;
-	int idx = 0;
-	int line = 0;
-	String error_str;
-	bool error = false;
-	Variant value;
-
-	Vector<ClassDecl> classes;
-
-	enum Token {
-		TK_BRACKET_OPEN,
-		TK_BRACKET_CLOSE,
-		TK_CURLY_BRACKET_OPEN,
-		TK_CURLY_BRACKET_CLOSE,
-		TK_PERIOD,
-		TK_COLON,
-		TK_COMMA,
-		TK_SYMBOL,
-		TK_IDENTIFIER,
-		TK_STRING,
-		TK_NUMBER,
-		TK_OP_LESS,
-		TK_OP_GREATER,
-		TK_EOF,
-		TK_ERROR,
-		TK_MAX
-	};
-
-	static const char *token_names[TK_MAX];
-	static String get_token_name(Token p_token);
-
-	Token get_token();
-
-	Error _skip_generic_type_params();
-
-	Error _parse_type_full_name(String &r_full_name);
-	Error _parse_class_base(Vector<String> &r_base);
-	Error _parse_type_constraints();
-	Error _parse_namespace_name(String &r_name, int &r_curly_stack);
-
-public:
-	Error parse(const String &p_code);
-	Error parse_file(const String &p_filepath);
-
-	String get_error();
-
-	Vector<ClassDecl> get_classes();
-};
-
-#endif // SCRIPT_CLASS_PARSER_H

+ 22 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs

@@ -0,0 +1,22 @@
+using System;
+
+namespace Godot
+{
+    [AttributeUsage(AttributeTargets.Assembly)]
+    public class AssemblyHasScriptsAttribute : Attribute
+    {
+        private readonly bool requiresLookup;
+        private readonly System.Type[] scriptTypes;
+
+        public AssemblyHasScriptsAttribute()
+        {
+            requiresLookup = true;
+        }
+
+        public AssemblyHasScriptsAttribute(System.Type[] scriptTypes)
+        {
+            requiresLookup = false;
+            this.scriptTypes = scriptTypes;
+        }
+    }
+}

+ 9 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace Godot
+{
+    [AttributeUsage(AttributeTargets.Class)]
+    public class DisableGodotGeneratorsAttribute : Attribute
+    {
+    }
+}

+ 15 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace Godot
+{
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+    public class ScriptPathAttribute : Attribute
+    {
+        private string path;
+
+        public ScriptPathAttribute(string path)
+        {
+            this.path = path;
+        }
+    }
+}

+ 3 - 0
modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj

@@ -14,9 +14,12 @@
   <ItemGroup>
     <Compile Include="Core\AABB.cs" />
     <Compile Include="Core\Array.cs" />
+    <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
+    <Compile Include="Core\Attributes\DisableGodotGeneratorsAttribute.cs" />
     <Compile Include="Core\Attributes\ExportAttribute.cs" />
     <Compile Include="Core\Attributes\GodotMethodAttribute.cs" />
     <Compile Include="Core\Attributes\RPCAttributes.cs" />
+    <Compile Include="Core\Attributes\ScriptPathAttribute.cs" />
     <Compile Include="Core\Attributes\SignalAttribute.cs" />
     <Compile Include="Core\Attributes\ToolAttribute.cs" />
     <Compile Include="Core\Basis.cs" />

+ 1 - 0
modules/mono/mono_gd/gd_mono.cpp

@@ -1006,6 +1006,7 @@ bool GDMono::_load_project_assembly() {
 
 	if (success) {
 		mono_assembly_set_main(project_assembly->get_assembly());
+		CSharpLanguage::get_singleton()->lookup_scripts_in_assembly(project_assembly);
 	}
 
 	return success;

+ 39 - 64
modules/mono/mono_gd/gd_mono_assembly.cpp

@@ -345,6 +345,45 @@ String GDMonoAssembly::get_path() const {
 	return String::utf8(mono_image_get_filename(image));
 }
 
+bool GDMonoAssembly::has_attribute(GDMonoClass *p_attr_class) {
+#ifdef DEBUG_ENABLED
+	ERR_FAIL_NULL_V(p_attr_class, false);
+#endif
+
+	if (!attrs_fetched) {
+		fetch_attributes();
+	}
+
+	if (!attributes) {
+		return false;
+	}
+
+	return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr());
+}
+
+MonoObject *GDMonoAssembly::get_attribute(GDMonoClass *p_attr_class) {
+#ifdef DEBUG_ENABLED
+	ERR_FAIL_NULL_V(p_attr_class, nullptr);
+#endif
+
+	if (!attrs_fetched) {
+		fetch_attributes();
+	}
+
+	if (!attributes) {
+		return nullptr;
+	}
+
+	return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr());
+}
+
+void GDMonoAssembly::fetch_attributes() {
+	ERR_FAIL_COND(attributes != nullptr);
+
+	attributes = mono_custom_attrs_from_assembly(assembly);
+	attrs_fetched = true;
+}
+
 GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) {
 	ERR_FAIL_NULL_V(image, nullptr);
 
@@ -390,70 +429,6 @@ GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) {
 	return wrapped_class;
 }
 
-GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) {
-	GDMonoClass *match = nullptr;
-
-	if (gdobject_class_cache_updated) {
-		Map<StringName, GDMonoClass *>::Element *result = gdobject_class_cache.find(p_class);
-
-		if (result) {
-			match = result->get();
-		}
-	} else {
-		List<GDMonoClass *> nested_classes;
-
-		int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF);
-
-		for (int i = 1; i < rows; i++) {
-			MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF);
-
-			if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) {
-				continue;
-			}
-
-			GDMonoClass *current = get_class(mono_class);
-
-			if (!current) {
-				continue;
-			}
-
-			nested_classes.push_back(current);
-
-			if (!match && current->get_name() == p_class) {
-				match = current;
-			}
-
-			while (!nested_classes.is_empty()) {
-				GDMonoClass *current_nested = nested_classes.front()->get();
-				nested_classes.pop_front();
-
-				void *iter = nullptr;
-
-				while (true) {
-					MonoClass *raw_nested = mono_class_get_nested_types(current_nested->get_mono_ptr(), &iter);
-
-					if (!raw_nested) {
-						break;
-					}
-
-					GDMonoClass *nested_class = get_class(raw_nested);
-
-					if (nested_class) {
-						gdobject_class_cache.insert(nested_class->get_name(), nested_class);
-						nested_classes.push_back(nested_class);
-					}
-				}
-			}
-
-			gdobject_class_cache.insert(current->get_name(), current);
-		}
-
-		gdobject_class_cache_updated = true;
-	}
-
-	return match;
-}
-
 GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
 	if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) {
 		return GDMono::get_singleton()->get_corlib_assembly();

+ 8 - 5
modules/mono/mono_gd/gd_mono_assembly.h

@@ -71,13 +71,13 @@ class GDMonoAssembly {
 	MonoImage *image;
 	MonoAssembly *assembly;
 
+	bool attrs_fetched = false;
+	MonoCustomAttrInfo *attributes = nullptr;
+
 #ifdef GD_MONO_HOT_RELOAD
 	uint64_t modified_time = 0;
 #endif
 
-	bool gdobject_class_cache_updated = false;
-	Map<StringName, GDMonoClass *> gdobject_class_cache;
-
 	HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes;
 	Map<MonoClass *, GDMonoClass *> cached_raw;
 
@@ -111,11 +111,14 @@ public:
 
 	String get_path() const;
 
+	bool has_attribute(GDMonoClass *p_attr_class);
+	MonoObject *get_attribute(GDMonoClass *p_attr_class);
+
+	void fetch_attributes();
+
 	GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name);
 	GDMonoClass *get_class(MonoClass *p_mono_class);
 
-	GDMonoClass *get_object_derived_class(const StringName &p_class);
-
 	static String find_assembly(const String &p_name);
 
 	static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String());

+ 10 - 0
modules/mono/mono_gd/gd_mono_cache.cpp

@@ -148,6 +148,11 @@ void CachedData::clear_godot_api_cache() {
 	class_PuppetSyncAttribute = nullptr;
 	class_GodotMethodAttribute = nullptr;
 	field_GodotMethodAttribute_methodName = nullptr;
+	class_ScriptPathAttribute = nullptr;
+	field_ScriptPathAttribute_path = nullptr;
+	class_AssemblyHasScriptsAttribute = nullptr;
+	field_AssemblyHasScriptsAttribute_requiresLookup = nullptr;
+	field_AssemblyHasScriptsAttribute_scriptTypes = nullptr;
 
 	field_GodotObject_ptr = nullptr;
 	field_StringName_ptr = nullptr;
@@ -272,6 +277,11 @@ void update_godot_api_cache() {
 	CACHE_CLASS_AND_CHECK(PuppetSyncAttribute, GODOT_API_CLASS(PuppetSyncAttribute));
 	CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute));
 	CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName"));
+	CACHE_CLASS_AND_CHECK(ScriptPathAttribute, GODOT_API_CLASS(ScriptPathAttribute));
+	CACHE_FIELD_AND_CHECK(ScriptPathAttribute, path, CACHED_CLASS(ScriptPathAttribute)->get_field("path"));
+	CACHE_CLASS_AND_CHECK(AssemblyHasScriptsAttribute, GODOT_API_CLASS(AssemblyHasScriptsAttribute));
+	CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, requiresLookup, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("requiresLookup"));
+	CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, scriptTypes, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("scriptTypes"));
 
 	CACHE_FIELD_AND_CHECK(GodotObject, ptr, CACHED_CLASS(GodotObject)->get_field(BINDINGS_PTR_FIELD));
 	CACHE_FIELD_AND_CHECK(StringName, ptr, CACHED_CLASS(StringName)->get_field(BINDINGS_PTR_FIELD));

+ 5 - 0
modules/mono/mono_gd/gd_mono_cache.h

@@ -119,6 +119,11 @@ struct CachedData {
 	GDMonoClass *class_PuppetSyncAttribute;
 	GDMonoClass *class_GodotMethodAttribute;
 	GDMonoField *field_GodotMethodAttribute_methodName;
+	GDMonoClass *class_ScriptPathAttribute;
+	GDMonoField *field_ScriptPathAttribute_path;
+	GDMonoClass *class_AssemblyHasScriptsAttribute;
+	GDMonoField *field_AssemblyHasScriptsAttribute_requiresLookup;
+	GDMonoField *field_AssemblyHasScriptsAttribute_scriptTypes;
 
 	GDMonoField *field_GodotObject_ptr;
 	GDMonoField *field_StringName_ptr;