Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ jobs:
with:
productNamespacePrefix: "Refit"
srcFolder: "./"
installWorkflows: true
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
1 change: 0 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ jobs:
configuration: Release
productNamespacePrefix: "Refit"
srcFolder: "./"
installWorkflows: false
secrets:
SIGN_ACCOUNT_NAME: ${{ secrets.SIGN_ACCOUNT_NAME }}
SIGN_PROFILE_NAME: ${{ secrets.SIGN_PROFILE_NAME }}
Expand Down
4 changes: 3 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)buildtask.snk</AssemblyOriginatorKeyFile>
<!--<SignAssembly>true</SignAssembly>-->
<RefitTargets>net462;netstandard2.0;net8.0;net9.0</RefitTargets>
<RefitTargets Condition="$([MSBuild]::IsOsPlatform('Windows'))">net462</RefitTargets>
<RefitTestTargets>net8.0;net9.0;net10.0</RefitTestTargets>
<RefitTargets>$(RefitTargets);netstandard2.0;$(RefitTestTargets)</RefitTargets>
<NoWarn>IDE0040;CA1054;CA1510</NoWarn>
</PropertyGroup>

Expand Down
30 changes: 21 additions & 9 deletions InterfaceStubGenerator.Shared/Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ UniqueNameBuilder uniqueNames
),
};

WriteMethodOpening(source, methodModel, !isTopLevel, isAsync);
var isExplicit = methodModel.IsExplicitInterface || !isTopLevel;
WriteMethodOpening(source, methodModel, isExplicit, isExplicit, isAsync);

// Build the list of args for the array
var argArray = methodModel
Expand All @@ -219,10 +220,18 @@ UniqueNameBuilder uniqueNames
? $", new global::System.Type[] {{ {string.Join(", ", genericArray)} }}"
: string.Empty;

// Normalize method lookup key: strip explicit interface prefix if present (e.g. IFoo.Bar -> Bar)
var lookupName = methodModel.Name;
var lastDotIndex = lookupName.LastIndexOf('.');
if (lastDotIndex >= 0 && lastDotIndex < lookupName.Length - 1)
{
lookupName = lookupName.Substring(lastDotIndex + 1);
}

source.WriteLine(
$"""
var ______arguments = {argumentsArrayString};
var ______func = requestBuilder.BuildRestResultFuncForMethod("{methodModel.Name}", {parameterTypesExpression}{genericString} );
var ______func = requestBuilder.BuildRestResultFuncForMethod("{lookupName}", {parameterTypesExpression}{genericString} );
{@return}({returnType})______func(this.Client, ______arguments){configureAwait};
"""
Expand All @@ -233,7 +242,8 @@ UniqueNameBuilder uniqueNames

private static void WriteNonRefitMethod(SourceWriter source, MethodModel methodModel)
{
WriteMethodOpening(source, methodModel, true);
var isExplicit = methodModel.IsExplicitInterface;
WriteMethodOpening(source, methodModel, isExplicit, isExplicit);

source.WriteLine(
@"throw new global::System.NotImplementedException(""Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument."");"
Expand All @@ -242,9 +252,6 @@ private static void WriteNonRefitMethod(SourceWriter source, MethodModel methodM
WriteMethodClosing(source);
}

// TODO: This assumes that the Dispose method is a void that takes no parameters.
// The previous version did not.
// Does the bool overload cause an issue here.
private static void WriteDisposableMethod(SourceWriter source)
{
source.WriteLine(
Expand Down Expand Up @@ -293,6 +300,7 @@ UniqueNameBuilder uniqueNames
private static void WriteMethodOpening(
SourceWriter source,
MethodModel methodModel,
bool isDerivedExplicitImpl,
bool isExplicitInterface,
bool isAsync = false
)
Expand All @@ -308,7 +316,12 @@ private static void WriteMethodOpening(

if (isExplicitInterface)
{
builder.Append(@$"{methodModel.ContainingType}.");
var ct = methodModel.ContainingType;
if (!ct.StartsWith("global::"))
{
ct = "global::" + ct;
}
builder.Append(@$"{ct}.");
}
builder.Append(@$"{methodModel.DeclaredMethod}(");

Expand All @@ -318,7 +331,6 @@ private static void WriteMethodOpening(
foreach (var param in methodModel.Parameters)
{
var annotation = param.Annotation;

list.Add($@"{param.Type}{(annotation ? '?' : string.Empty)} @{param.MetadataName}");
}

Expand All @@ -330,7 +342,7 @@ private static void WriteMethodOpening(
source.WriteLine();
source.WriteLine(builder.ToString());
source.Indentation++;
GenerateConstraints(source, methodModel.Constraints, isExplicitInterface);
GenerateConstraints(source, methodModel.Constraints, isDerivedExplicitImpl || isExplicitInterface);
source.Indentation--;
source.WriteLine("{");
source.Indentation++;
Expand Down
2 changes: 1 addition & 1 deletion InterfaceStubGenerator.Shared/ImmutableEquatableArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ static int Combine(int h1, int h2)

IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator();

public struct Enumerator
public record struct Enumerator
{
private readonly T[] _values;
private int _index;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Models\TypeConstraint.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Models\WellKnownTypes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parser.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Polyfills\IndexRange.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SourceWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UniqueNameBuilder.cs" />
</ItemGroup>
Expand Down
31 changes: 2 additions & 29 deletions InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,8 @@

namespace Refit.Generator
{
// * Search for all Interfaces, find the method definitions
// and make sure there's at least one Refit attribute on one
// * Generate the data we need for the template based on interface method
// defn's

/// <summary>
/// InterfaceStubGeneratorV2.
/// InterfaceStubGenerator.
/// </summary>
[Generator]
#if ROSLYN_4
Expand All @@ -28,7 +23,6 @@ public class InterfaceStubGenerator : ISourceGenerator
private const string TypeParameterVariableName = "______typeParameters";

#if !ROSLYN_4

/// <summary>
/// Executes the specified context.
/// </summary>
Expand All @@ -51,13 +45,11 @@ out var refitInternalNamespace
context.CancellationToken
);

// Emit diagnostics
foreach (var diagnostic in parseStep.diagnostics)
{
context.ReportDiagnostic(diagnostic);
}

// Emit interface stubs
foreach (var interfaceModel in parseStep.contextGenerationSpec.Interfaces)
{
var interfaceText = Emitter.EmitInterface(interfaceModel);
Expand All @@ -67,7 +59,6 @@ out var refitInternalNamespace
);
}

// Emit PreserveAttribute and Generated.Initialize
Emitter.EmitSharedCode(
parseStep.contextGenerationSpec,
(name, code) => context.AddSource(name, code)
Expand All @@ -76,15 +67,9 @@ out var refitInternalNamespace
#endif

#if ROSLYN_4

/// <summary>
/// Initializes the specified context.
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
/// <inheritdoc/>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// We're looking for methods with an attribute that are in an interface
var candidateMethodsProvider = context.SyntaxProvider.CreateSyntaxProvider(
(syntax, cancellationToken) =>
syntax
Expand All @@ -96,8 +81,6 @@ is MethodDeclarationSyntax
(context, cancellationToken) => (MethodDeclarationSyntax)context.Node
);

// We also look for interfaces that derive from others, so we can see if any base methods contain
// Refit methods
var candidateInterfacesProvider = context.SyntaxProvider.CreateSyntaxProvider(
(syntax, cancellationToken) =>
syntax is InterfaceDeclarationSyntax { BaseList: not null },
Expand Down Expand Up @@ -146,9 +129,6 @@ out var refitInternalNamespace
}
);

// output the diagnostics
// use `ImmutableEquatableArray` to cache cases where there are no diagnostics
// otherwise the subsequent steps will always rerun.
var diagnostics = parseStep
.Select(static (x, _) => x.diagnostics.ToImmutableEquatableArray())
.WithTrackingName(RefitGeneratorStepName.ReportDiagnostics);
Expand All @@ -168,14 +148,11 @@ out var refitInternalNamespace
}
);
}

#else

/// <summary>
/// Initializes the specified context.
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
Expand All @@ -189,7 +166,6 @@ class SyntaxReceiver : ISyntaxReceiver

public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
// We're looking for methods with an attribute that are in an interfaces
if (
syntaxNode is MethodDeclarationSyntax methodDeclarationSyntax
&& methodDeclarationSyntax.Parent is InterfaceDeclarationSyntax
Expand All @@ -199,15 +175,12 @@ syntaxNode is MethodDeclarationSyntax methodDeclarationSyntax
CandidateMethods.Add(methodDeclarationSyntax);
}

// We also look for interfaces that derive from others, so we can see if any base methods contain
// Refit methods
if (syntaxNode is InterfaceDeclarationSyntax iface && iface.BaseList is not null)
{
CandidateInterfaces.Add(iface);
}
}
}

#endif
}

Expand Down
7 changes: 5 additions & 2 deletions InterfaceStubGenerator.Shared/Models/MethodModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Refit.Generator;
using System.Collections.Immutable;

namespace Refit.Generator;

internal sealed record MethodModel(
string Name,
Expand All @@ -7,7 +9,8 @@ internal sealed record MethodModel(
string DeclaredMethod,
ReturnTypeInfo ReturnTypeMetadata,
ImmutableEquatableArray<ParameterModel> Parameters,
ImmutableEquatableArray<TypeConstraint> Constraints
ImmutableEquatableArray<TypeConstraint> Constraints,
bool IsExplicitInterface
);

internal enum ReturnTypeInfo : byte
Expand Down
27 changes: 26 additions & 1 deletion InterfaceStubGenerator.Shared/Models/WellKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,41 @@

namespace Refit.Generator;

/// <summary>
/// WellKnownTypes.
/// </summary>
public class WellKnownTypes(Compilation compilation)
{
readonly Dictionary<string, INamedTypeSymbol?> cachedTypes = new();
readonly Dictionary<string, INamedTypeSymbol?> cachedTypes = [];

/// <summary>
/// Gets this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public INamedTypeSymbol Get<T>() => Get(typeof(T));

/// <summary>
/// Gets the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">Could not get name of type " + type</exception>
public INamedTypeSymbol Get(Type type)
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
Comment on lines +27 to +30
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

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

[nitpick] This null check is redundant since the parameter already has nullable annotations and the framework will handle null reference validation appropriately.

Suggested change
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}

Copilot uses AI. Check for mistakes.

return Get(type.FullName ?? throw new InvalidOperationException("Could not get name of type " + type));
}

/// <summary>
/// Tries the get.
/// </summary>
/// <param name="typeFullName">Full name of the type.</param>
/// <returns></returns>
public INamedTypeSymbol? TryGet(string typeFullName)
{
if (cachedTypes.TryGetValue(typeFullName, out var typeSymbol))
Expand Down
Loading
Loading