https://github.com/emik03/emik.sourcegenerators.thesquarehole
Adds structural typing to C#.
https://github.com/emik03/emik.sourcegenerators.thesquarehole
Last synced: 6 months ago
JSON representation
Adds structural typing to C#.
- Host: GitHub
- URL: https://github.com/emik03/emik.sourcegenerators.thesquarehole
- Owner: Emik03
- License: mpl-2.0
- Created: 2023-08-09T19:41:49.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2025-01-01T10:51:52.000Z (over 1 year ago)
- Last Synced: 2025-10-25T23:55:15.415Z (10 months ago)
- Language: C#
- Size: 571 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# Emik.SourceGenerators.TheSquareHole
[](https://www.nuget.org/packages/Emik.SourceGenerators.TheSquareHole)
[](https://github.com/Emik03/Emik.SourceGenerators.TheSquareHole/blob/main/LICENSE)
"And up next, a cylinder. Hmm, I think that goes in... the square hole!"
Adds structural typing to C#. Made as a celebration for my 20th birthday on August 8th 2023.
This project has a dependency to [Emik.Morsels](https://github.com/Emik03/Emik.Morsels), if you are building this project, refer to its [README](https://github.com/Emik03/Emik.Morsels/blob/main/README.md) first.
---
- [Why](#why)
- [How](#how)
- [Configure](#configure)
- [Contribute](#contribute)
- [License](#license)
---
## Why
Despite the presentation of the project, the project itself is treated very seriously and addresses a real-world problem.
When creating software, you are encouraged to write code that is easily extensible and reusable. Interfaces are a fantastic way of achieving this, often allowing you to reuse methods that take said interface as a parameter. Part of the problem however is having to juggle the signatures of each interface in your head.
If you are creating a type specifically to implement an interface, this isn't much of a problem, but if you want to maximize the re-usability of a type, particularly if you make a type whose purpose is fairly generic, then you may often sit thinking about each interface you may consider adding.
Languages like Scala solve this issue using Structural Typing. If a type has the same declaring members as an interface, then it derives it.
## How
This source generator looks at every interface accessible from your assembly and determines whether it is able to implement the interface.
Empty interfaces, alongside attributes marked with [`ObsoleteAttribute`](https://learn.microsoft.com/en-us/dotnet/api/system.obsoleteattribute?view=net-7.0) are **not considered part of the search**, as these interfaces tend to function more like attributes, and should therefore be opt-in.
If the interface contains generics, then the source generator performs **Type Substitution**: It considers every type declared within any type, including itself. For instance, take a look at the following type:
```csharp
public class A(int i);
```
The type `A` declares a method with a parameter `int`, with nothing else. Therefore the candidates are `int` and `A`.
This results in the source generator being extremely flexible, and accounts for every possible implementation. For instance:
```csharp
public partial record Cylinder(T1 Second)
{
public int First => 0;
}
interface ISquare
{
T1 First { get; }
T2 Second { get; }
}
```
...generates...
```csharp
//
#nullable enable
partial record Cylinder : global::ISquare,
global::System.Numerics.IEqualityOperators, global::Cylinder, bool>
{
}
```
The hard upper limit for type substitution with generics are exactly **3** of them. This does not include generics which are self-constrained.
While this analyzer does perform a lot of tricks to squeeze performance, type substituion may be still too expensive on your machine. Refer to [Configure](#configure) in that case.
## Configure
Use `.editorconfig`/`.globalconfig` to configure this source generator:
---
| Option | `the_square_hole_enable_concurrency` |
|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Summary | Determines whether to enable concurrency for inspections. |
| Remarks | Concurrency is only faster in large projects due to overhead in initializing concurrent behavior, hence why it's disabled by default. |
| Obsolete | This option is deprecated starting from `1.1` and onwards due to the migration of the favorable [`IIncrementalGenerator`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.iincrementalgenerator) over [`ISourceGenerator`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.isourcegenerator). |
| Type | `bool` |
| Default | `false` |
---
| Option | `the_square_hole_include_nullability` |
|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Summary | Determines whether or not to include nullability as a restriction. |
| Remarks | `A(string s)` and `A(string? s)` are equal interface-wise despite the unequal signature metadata. This can however violate the contract of the interface, and you will get the suggestion to change the signature, which would be a breaking feature in an existing API. |
| Type | `bool` |
| Default | `false` |
---
| Option | `the_square_hole_include_parameter_name` |
|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Summary | Determines whether or not to include parameter names as a restriction. |
| Remarks | `A(int a)` and `A(int b)` are equal interface-wise despite the unequal parameter naming. Some analyzers encourage renaming parameters when such a scenario occurs, which would be a breaking feature in an existing API. |
| Type | `bool` |
| Default | `false` |
---
| Option | `the_square_hole_max_substitution_depth` |
|---------|-------------------------------------------------------------------------------------------------|
| Summary | Determines the maximum number of type substitutions allowed for a given interface. |
| Remarks | Lower = faster, higher = better inference of generics. Lower it if you face performance issues. |
| Type | `0..=3` |
| Default | `3` |
---
## Contribute
Issues and pull requests are welcome to help this repository be the best it can be.
## License
This repository falls under the [MPL-2 license](https://www.mozilla.org/en-US/MPL/2.0/).