FastScriptReload icon indicating copy to clipboard operation
FastScriptReload copied to clipboard

Support partial classes

Open Jlabarca opened this issue 1 year ago • 1 comments

Addressing support partial classes

A MergePartials step is added, where syntax trees containing partial definitions get rebuilded to contain all partials found combined.

Steps done for it:

  1. Check if the input syntax tree contains any partial types.
  2. If partial types exist, process the current tree:
    • Extract all using directives
    • Collect all type declarations
  3. Search for other files with partial declarations in the same directory (recurive).
  4. Process each found file, collecting their type declarations and using directives.
  5. Combine all partial declarations of the same type.
  6. Create a new syntax tree with all usings, fields and methods

Jlabarca avatar Jun 25 '24 02:06 Jlabarca

Sorry for delayed response - been away. Looking good - glad you've added UnitTests as well. Looking forward to reviewing.

Much needed feature btw. There's been quite a lot question about this one over time.

Thanks for contributing!

handzlikchris avatar Jul 02 '24 06:07 handzlikchris

Hoping this will be merged soon! 🙏

RunninglVlan avatar Sep 02 '24 17:09 RunninglVlan

Looks good - thanks again for contribution!

handzlikchris avatar Sep 03 '24 08:09 handzlikchris

@RunninglVlan done some testing and merged to master. Looks good - if you have a chance to try do let us know how you got on.

handzlikchris avatar Sep 03 '24 09:09 handzlikchris

@handzlikchris, it might not be related to this PR, but there are not many partial classes yet in our current project, so I'm sharing this finding. I have both interface and partial class in the same file and here's what FSR created: Was:

public interface IMenu {
  // ...
}

public partial class Menu : MonoBehaviour, IMenu {
  // ...
}

FSR generated (doesn't compile):

public interface IMenu__Patched_{
  // ...
}

public partial  class Menu__Patched_:MonoBehaviour,IMenu { // NOTE: Incorrect interface
  // ...
}

RunninglVlan avatar Sep 04 '24 18:09 RunninglVlan

Hmm, thanks - why does that not compile? Did you change interface? - btw would be good to start as a separate issue.

handzlikchris avatar Sep 05 '24 07:09 handzlikchris

@Jlabarca Seems I understood what's the issue. It's not about both interface and partial class in one file. Partial class merging doesn't keep namespaces. I also noticed that tests don't check that namespace exists in the merged file, although test classes have them 😅 Without namespace it works with interface in the same file and without it.

RunninglVlan avatar Sep 05 '24 11:09 RunninglVlan

Here's my test: Partial classes:

// TestClass1.1.cs
using TeamHalfBeard.Core;
using UnityEngine.InputSystem;

namespace TestNamespace {
    [Service(typeof(TestClass1))]
    public partial class TestClass1 : IUpdatable {
        public void Update(float deltaTime) {
            if (Keyboard.current.kKey.wasPressedThisFrame) {
                LogMessage("kek");
            }
        }
    }
}

// TestClass1.2.cs
using UnityEngine;

namespace TestNamespace {
    public partial class TestClass1 {
        private void LogMessage(string message) {
            Debug.Log($"{GetType().Name}.{message}");
        }
    }
}

Partial classes (result):

using TeamHalfBeard.Core;
using UnityEngine.InputSystem;
using UnityEngine;
[assembly:System.Runtime.CompilerServices.InternalsVisibleTo("Assembly-CSharp")]    [Service(typeof(TestClass1))]
    public partial  class TestClass1__Patched_:IUpdatable {
        public void Update(float deltaTime) {
            if (Keyboard.current.kKey.wasPressedThisFrame) {
                LogMessage("lol");
            }
        }
        private void LogMessage(string message) {
            Debug.Log($"{GetType().Name}.{message}");
        }

private static global::System.Collections.Generic.Dictionary<string, global::System.Func<object>> __Patched_NewFieldNameToInitialValueFn = new global::System.Collections.Generic.Dictionary<string, global::System.Func<object>>
{
};


private static global::System.Collections.Generic.Dictionary<string, global::System.Func<object>> __Patched_NewFieldsToGetTypeFnDictionaryFieldName = new global::System.Collections.Generic.Dictionary<string, global::System.Func<object>>
{
};

    }

Normal class:

// TestClass2.cs
using TeamHalfBeard.Core;
using UnityEngine;
using UnityEngine.InputSystem;

namespace TestNamespace {
    [Service(typeof(TestClass2))]
    public class TestClass2 : IUpdatable {
        public void Update(float deltaTime) {
            if (Keyboard.current.kKey.wasPressedThisFrame) {
                LogMessage("lol");
            }
        }

        private void LogMessage(string message) {
            Debug.Log($"{GetType().Name}.{message}");
        }
    }
}

Normal class (result):

using TeamHalfBeard.Core;
using UnityEngine;
using UnityEngine.InputSystem;

namespace TestNamespace {
    [Service(typeof(TestClass2))]
    public class TestClass2__Patched_: IUpdatable {
        public void Update(float deltaTime) {
            if (Keyboard.current.kKey.wasPressedThisFrame) {
                LogMessage("lol");
            }
        }

        private void LogMessage(string message) {
            Debug.Log($"{GetType().Name}.{message}");
        }

private static global::System.Collections.Generic.Dictionary<string, global::System.Func<object>> __Patched_NewFieldNameToInitialValueFn = new global::System.Collections.Generic.Dictionary<string, global::System.Func<object>>
{
};


private static global::System.Collections.Generic.Dictionary<string, global::System.Func<object>> __Patched_NewFieldsToGetTypeFnDictionaryFieldName = new global::System.Collections.Generic.Dictionary<string, global::System.Func<object>>
{
};

    }
}

RunninglVlan avatar Sep 05 '24 12:09 RunninglVlan

@handzlikchris, created #137

RunninglVlan avatar Sep 07 '24 07:09 RunninglVlan