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

Add logic to emit #nullable in the generated files if required #1069

Merged
merged 2 commits into from
Feb 10, 2021
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
108 changes: 76 additions & 32 deletions InterfaceStubGenerator.Core/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,47 +114,64 @@ internal static partial class Generated
compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(generatedClassText, Encoding.UTF8), options));

// Check the candidates and keep the ones we're actually interested in

var interfaceToNullableEnabledMap = new Dictionary<INamedTypeSymbol, bool>();
var methodSymbols = new List<IMethodSymbol>();
foreach (var method in receiver.CandidateMethods)
foreach (var group in receiver.CandidateMethods.GroupBy(m => m.SyntaxTree))
{
var model = compilation.GetSemanticModel(method.SyntaxTree);

// Get the symbol being declared by the method
var methodSymbol = model.GetDeclaredSymbol(method);
if (IsRefitMethod(methodSymbol, httpMethodBaseAttributeSymbol))
var model = compilation.GetSemanticModel(group.Key);
foreach (var method in group)
{
methodSymbols.Add(methodSymbol!);
// Get the symbol being declared by the method
var methodSymbol = model.GetDeclaredSymbol(method);
if (IsRefitMethod(methodSymbol, httpMethodBaseAttributeSymbol))
{
var isAnnotated = context.Compilation.Options.NullableContextOptions == NullableContextOptions.Enable ||
model.GetNullableContext(method.SpanStart) == NullableContext.Enabled;
interfaceToNullableEnabledMap[methodSymbol!.ContainingType] = isAnnotated;

methodSymbols.Add(methodSymbol!);
}
}
}
}

var interfaces = methodSymbols.GroupBy(m => m.ContainingType).ToDictionary(g => g.Key, v => v.ToList());
var interfaces = methodSymbols.GroupBy(m => m.ContainingType)
.ToDictionary(g => g.Key, v => v.ToList());

// Look through the candidate interfaces
var interfaceSymbols = new List<INamedTypeSymbol>();
foreach(var iface in receiver.CandidateInterfaces)
foreach(var group in receiver.CandidateInterfaces.GroupBy(i => i.SyntaxTree))
{
var model = compilation.GetSemanticModel(iface.SyntaxTree);

// get the symbol belonging to the interface
var ifaceSymbol = model.GetDeclaredSymbol(iface);

// See if we already know about it, might be a dup
if (ifaceSymbol is null || interfaces.ContainsKey(ifaceSymbol))
continue;

// The interface has no refit methods, but its base interfaces might
var hasDerivedRefit = ifaceSymbol.AllInterfaces
.SelectMany(i => i.GetMembers().OfType<IMethodSymbol>())
.Where(m => IsRefitMethod(m, httpMethodBaseAttributeSymbol))
.Any();

if(hasDerivedRefit)
var model = compilation.GetSemanticModel(group.Key);
foreach (var iface in group)
{
// Add the interface to the generation list with an empty set of methods
// The logic already looks for base refit methods
interfaces.Add(ifaceSymbol, new List<IMethodSymbol>());
// get the symbol belonging to the interface
var ifaceSymbol = model.GetDeclaredSymbol(iface);

// See if we already know about it, might be a dup
if (ifaceSymbol is null || interfaces.ContainsKey(ifaceSymbol))
continue;

// The interface has no refit methods, but its base interfaces might
var hasDerivedRefit = ifaceSymbol.AllInterfaces
.SelectMany(i => i.GetMembers().OfType<IMethodSymbol>())
.Where(m => IsRefitMethod(m, httpMethodBaseAttributeSymbol))
.Any();

if (hasDerivedRefit)
{
// Add the interface to the generation list with an empty set of methods
// The logic already looks for base refit methods
interfaces.Add(ifaceSymbol, new List<IMethodSymbol>() );
var isAnnotated = model.GetNullableContext(iface.SpanStart) == NullableContext.Enabled;

interfaceToNullableEnabledMap[ifaceSymbol] = isAnnotated;
}
}
}


var supportsNullable = ((CSharpParseOptions)context.ParseOptions).LanguageVersion >= LanguageVersion.CSharp8;

var keyCount = new Dictionary<string, int>();

Expand All @@ -165,7 +182,14 @@ internal static partial class Generated
// with a refit attribute on them. Types may contain other members, without the attribute, which we'll
// need to check for and error out on

var classSource = ProcessInterface(group.Key, group.Value, preserveAttributeSymbol, disposableInterfaceSymbol, httpMethodBaseAttributeSymbol, context);
var classSource = ProcessInterface(group.Key,
group.Value,
preserveAttributeSymbol,
disposableInterfaceSymbol,
httpMethodBaseAttributeSymbol,
supportsNullable,
interfaceToNullableEnabledMap[group.Key],
context);

var keyName = group.Key.Name;
if(keyCount.TryGetValue(keyName, out var value))
Expand All @@ -184,6 +208,8 @@ string ProcessInterface(INamedTypeSymbol interfaceSymbol,
ISymbol preserveAttributeSymbol,
ISymbol disposableInterfaceSymbol,
INamedTypeSymbol httpMethodBaseAttributeSymbol,
bool supportsNullable,
bool nullableEnabled,
GeneratorExecutionContext context)
{

Expand All @@ -210,7 +236,25 @@ string ProcessInterface(INamedTypeSymbol interfaceSymbol,
// Remove dots
ns = ns!.Replace(".", "");

var source = new StringBuilder($@"
// See what the nullable context is


var source = new StringBuilder();
if(supportsNullable)
{
source.Append("#nullable ");

if(nullableEnabled)
{
source.Append("enable");
}
else
{
source.Append("disable");
}
}

source.Append($@"
#pragma warning disable
namespace Refit.Implementation
{{
Expand Down Expand Up @@ -315,7 +359,7 @@ void ProcessRefitMethod(StringBuilder source, IMethodSymbol methodSymbol, bool i
argList.Add($"@{param.MetadataName}");
}

// List of types. For nullable one, wrap in ToNullable()
// List of types.
var typeList = new List<string>();
foreach(var param in methodSymbol.Parameters)
{
Expand Down
8 changes: 4 additions & 4 deletions Refit.Tests/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ internal static partial class Generated
#pragma warning restore
";

var output2 = @"
var output2 = @"#nullable disable
#pragma warning disable
namespace Refit.Implementation
{
Expand Down Expand Up @@ -387,7 +387,7 @@ public RefitTestsIGitHubApi(global::System.Net.Http.HttpClient client, global::R

#pragma warning restore
";
var output3 = @"
var output3 = @"#nullable disable
#pragma warning disable
namespace Refit.Implementation
{
Expand Down Expand Up @@ -445,7 +445,7 @@ public RefitTestsIGitHubApiDisposable(global::System.Net.Http.HttpClient client,

#pragma warning restore
";
var output4 = @"
var output4 = @"#nullable disable
#pragma warning disable
namespace Refit.Implementation
{
Expand Down Expand Up @@ -675,7 +675,7 @@ internal static partial class Generated
#pragma warning restore
";

var output2 = @"
var output2 = @"#nullable disable
#pragma warning disable
namespace Refit.Implementation
{
Expand Down
1 change: 1 addition & 0 deletions Refit.Tests/Refit.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp2.1;netcoreapp3.1;net461</TargetFrameworks>
<Deterministic>false</Deterministic> <!-- Some tests rely on CallerFilePath -->
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand Down