Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[VDG] AutoInterface Source Generator #11402

Merged
merged 17 commits into from
Sep 4, 2023

Conversation

ichthus1604
Copy link
Collaborator

@ichthus1604 ichthus1604 commented Aug 30, 2023

Motivation

The main objection to the architectural changes introduced by UI Decoupling is the proliferation of interfaces which solely exist for the purpose of unit testing. This concern is totally valid, and therefore this PR addresses that problem in a (IMO) very elegant way.

Solution

This PR introduces [AutoInterface] attribute, which might be applied to any class, and will create a source-generated equivalent interface, exposing all the public members of the class.

So, for instance, given the WalletModel class, simply applying [AutoInterface] to it will result in the following syntax tree being generated:

// <auto-generated />

#nullable enable
using DynamicData;
using NBitcoin;
using System;
using System.Collections.Generic;
using WalletWasabi.Blockchain.Transactions;
using WalletWasabi.Fluent.ViewModels.Wallets;
using WalletWasabi.Fluent.ViewModels.Wallets.Labels;
using WalletWasabi.Wallets;

namespace WalletWasabi.Fluent.Models.Wallets;

public partial class WalletModel: IWalletModel
{
}

public partial interface IWalletModel
{
    IWalletBalancesModel Balances { get; }

    IWalletAuthModel Auth { get; }

    IWalletLoadWorkflow Loader { get; }

    IWalletSettingsModel Settings { get; }

    IWalletPrivacyModel Privacy { get; }

    IWalletCoinjoinModel Coinjoin { get; }

    IObservable<IChangeSet<ICoinModel>> Coins { get; }

    IObservable<IChangeSet<IAddress, string>> Addresses { get; }

    string Name { get; }

    IObservable<WalletState> State { get; }

    IObservable<IChangeSet<TransactionSummary, uint256>> Transactions { get; }

    bool IsHardwareWallet { get; }

    bool IsWatchOnlyWallet { get; }

    IAddress GetNextReceiveAddress(IEnumerable<string> destinationLabels);

    IWalletInfoModel GetWalletInfo();

    IEnumerable<(string Label, int Score)> GetMostUsedLabels(Intent intent);
}

Compare this generated interface with the manually written IWalletModel:

public interface IWalletModel
{
	string Name { get; }

	IObservable<WalletState> State { get; }

	IObservable<IChangeSet<ICoinModel>> Coins { get; }

	IObservable<IChangeSet<TransactionSummary, uint256>> Transactions { get; }

	IObservable<IChangeSet<IAddress, string>> Addresses { get; }

	IWalletBalancesModel Balances { get; }

	bool IsHardwareWallet { get; }

	bool IsWatchOnlyWallet { get; }

	IWalletAuthModel Auth { get; }

	IWalletLoadWorkflow Loader { get; }

	IWalletSettingsModel Settings { get; }

	IWalletPrivacyModel Privacy { get; }

	IWalletCoinjoinModel Coinjoin { get; }

	IAddress GetNextReceiveAddress(IEnumerable<string> destinationLabels);

	IEnumerable<(string Label, int Score)> GetMostUsedLabels(Intent intent);

	IWalletInfoModel GetWalletInfo();
}

As you can see, both are identical, except for the order of the members, which in the case of the generated one is the declaration order of the members in the implementing class, except that properties are placed first in order to satisfy CodeFactor rules.

It is also important to note that this generator creates idiomatic C# code, meaning the code is very similar to the hand-written one, including things like:

  • using int instead of System.Int32
  • proper tuple syntax instead of System.ValueTuple<A,B>
  • using directives instead of fully qualified type names.
  • ? notation for nullable value and reference types (int? instead of System.Nullable<System.Int32>)

In a follow-up PR, I will remove all these hand-written interfaces from the entire project and replace them with the auto generated ones.

Benefits

  • We no longer have to maintain any of these interfaces
  • Code clutter is reduced.
  • VDG team PRs will "touch" less files
  • Adding a member to a class will automatically cause a compilation error in the "mocked" versions of the class in the Test project.

Copy link
Collaborator

@soosr soosr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cACK

@soosr soosr merged commit bf2dd24 into WalletWasabi:master Sep 4, 2023
5 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants