-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Async main codegen #18472
Async main codegen #18472
Conversation
@dotnet/roslyn-compiler This bit should be ready for a general review. I've added some pretty minimal tests that I plan on fleshing out in another PR. #Resolved |
@@ -221,7 +241,10 @@ private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModul | |||
moduleBeingBuilt.SetMethodBody(synthesizedEntryPoint, emittedBody); | |||
} | |||
|
|||
Debug.Assert((object)entryPoint != null || entryPointAndDiagnostics.Diagnostics.HasAnyErrors() || !compilation.Options.Errors.IsDefaultOrEmpty); | |||
if (addedDefinition && moduleBeingBuilt != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Braces on new lines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -196,8 +197,27 @@ private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModul | |||
diagnostics.AddRange(entryPointAndDiagnostics.Diagnostics); | |||
|
|||
var entryPoint = entryPointAndDiagnostics.MethodSymbol; | |||
var synthesizedEntryPoint = entryPoint as SynthesizedEntryPointSymbol; | |||
if (((object)synthesizedEntryPoint != null) && | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a comment to the top of the function noting that this returns the real entry point vs. the user defined one. The name of the function is ambiguous with this feature now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
bool addedDefinition = false; | ||
if (entryPoint is SynthesizedEntryPointSymbol s) | ||
{ | ||
synthesizedEntryPoint = s; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What case hits this branch of the if
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other two other subclasses of SynthesizedEntryPointSymbol are ScriptEntryPoint and SubmissionEntryPoint both of which (I think) are for scripting support.
|
||
// Task -> Void | ||
// Task<_> -> Int32 | ||
private static TypeSymbol TranslateReturnType(CSharpCompilation compilation, TypeSymbol returnType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Braces on new lines. Other occurences in this review that should be fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double blank line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
_paramKind = userMain.ParameterCount == 0 ? ParamKind.NoArgs : ParamKind.StringArrayArgs; | ||
_returnKind = GetReturnKind(compilation, userMain.ReturnType); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double blank line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
type: _userMain.ReturnType) | ||
{ WasCompilerGenerated = true }; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double blank line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still see this in latest source.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still see this in the latest source.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TyOverby still present
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually Done. I think I fixed another instance of this problem earlier...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clearly I need to update the auto-formatter in order to accommodate this
addedDefinition = true; | ||
} | ||
|
||
if ((synthesizedEntryPoint != null) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to use the ((object)synthesizedEntryPoint) != null
style of comparison here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
else if (entryPoint.HasAsyncMainReturnType(compilation) && compilation.LanguageVersion >= LanguageVersion.CSharp7_1) | ||
{ | ||
synthesizedEntryPoint = new AsyncForwardEntryPoint(compilation, diagnostics, entryPoint.ContainingType, entryPoint); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems odd that we are attaching the diagnostics
to only the async sythesized method symbol. What is the rationale for this?
In the case the diagnostics
parameter had existing diagnostics in it, possible for a bag, it would be attaching those errors to the generated main as well which seems incorrect. As say using entryPointAndDiagnostics.Diagnostics
. Even that though feels like we are double tracking diagnostics here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now the only place that the diagnosticsBag is used is during the GetRequiredMethod
lookup for GetAwaiter
and GetResult
.
// Task<_> -> Int32 | ||
private static TypeSymbol TranslateReturnType(CSharpCompilation compilation, TypeSymbol returnType) | ||
{ | ||
if (returnType.IsGenericTaskType(compilation)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: more code formatting issues all through this file. Please review the entire PR and correct these.
// The user-defined asyncrhonous main method. | ||
private MethodSymbol _userMain; | ||
private DiagnosticBag _diagnosticBag; | ||
private CSharpCompilation _compilation; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this stored as a field? It seems to only be used in the constructor of the type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, this reference must have survived some refactors.
internal sealed class AsyncForwardEntryPoint : SynthesizedEntryPointSymbol | ||
{ | ||
// The user-defined asyncrhonous main method. | ||
private MethodSymbol _userMain; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readonly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
private CSharpCompilation _compilation; | ||
|
||
private ReturnKind _returnKind; | ||
private ParamKind _paramKind; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
both of these can be readonly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
private ReturnKind _returnKind; | ||
private ParamKind _paramKind; | ||
|
||
private ImmutableArray<ParameterSymbol> _parameters; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readonly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
var syntax = this.GetSyntax(); | ||
|
||
var refKinds = Parameters.Length == 0 ? ImmutableArray<RefKind>.Empty : ImmutableArray.Create(RefKind.None); | ||
var arguments = new List<BoundExpression>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be optimized by using new List<BoundExpression>(capacity: Parameters.Length)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
receiverOpt: null, | ||
method: _userMain, | ||
arguments: arguments.ToImmutableArray(), | ||
argumentNamesOpt: ImmutableArray<string>.Empty, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we aren't replicated the names on the user authored Main method? True it's legal to generate source without parameter names but some tools still don't handle it gracefully.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh fine :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that you meant for this comment to be on another line though. Our main method parameter names is defined in the call to SynthesizedParameterSymbol.Create inside the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah you're correct. The use of ImmutableArray<string>.Empty
is correct in this location.
type: _userMain.ReturnType) | ||
{ WasCompilerGenerated = true }; | ||
|
||
// PROTOTYPE(async-main): Errors should likely be reported here instead of bailing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Likely we can just move this up into the creation of the type. This would also avoid the need to store the bag on the type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
SynthesizedEntryPointSymbol synthesizedEntryPoint = null; | ||
bool addedDefinition = false; | ||
if (entryPoint is SynthesizedEntryPointSymbol s) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is SynthesizedEntryPointSymbol s
followed by synthesizedEntryPoint = s;
seems more convoluted (to me at least) than:
var synthesizedEntryPoint = entryPoint as SynthesizedEntryPointSymbol;
if ((object)synthesizedEntryPoint == null)
{
// other cases
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doen.
else | ||
{ | ||
return compilation.GetSpecialType(SpecialType.System_Void); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using conditional operator here and in method below:
return returnType.IsGenericTaskType(compilation) ? ... : ...;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the style formatting for breaking this across multiple lines?
I'm partial to
return returnType.IsGenericTaskType(compilation)
? compilation.GetSpecialType(SpecialType.System_Int32)
: compilation.GetSpecialType(SpecialType.System_Void);
foreach (var parameterSymbol in Parameters) | ||
{ | ||
arguments.Add(new BoundParameter(syntax, parameterSymbol, parameterSymbol.Type)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var arguments = Parameters.SelectAsArray((p, s) => new BoundParameter(s, p, p.Type), syntax);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually had this code originally but removed it because I thought that LINQ was more-or-less banned in the compiler
|
||
internal AsyncForwardEntryPoint(CSharpCompilation compilation, DiagnosticBag diagnosticBag, NamedTypeSymbol containingType, MethodSymbol userMain): | ||
base(containingType, TranslateReturnType(compilation, userMain.ReturnType)) { | ||
Debug.Assert(userMain != null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug.Assert
seems unnecessary since userMain
was dereferenced in base
call, and also below. (NullReferenceException seems sufficient.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
{ | ||
var syntax = this.GetSyntax(); | ||
|
||
var refKinds = Parameters.Length == 0 ? ImmutableArray<RefKind>.Empty : ImmutableArray.Create(RefKind.None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems strange to assume there is exactly zero or one parameter for refKinds
but not for arguments
below. Consider generalizing this. Perhaps: var refKinds = Parameters.SelectAsArray(p => RefKind.None);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better yet, argumentRefKindsOpt
is an optional parameter so we can pass in default(ImmutableArray<RefKind>)
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
isDelegateCall: false, | ||
expanded: false, | ||
invokedAsExtensionMethod: false, | ||
argsToParamsOpt: ImmutableArray<int>.Empty, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe argumentNamesOpt
and argsToParamsOpt
should be default(ImmutableArray<T>)
rather than Empty
if not set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -334,14 +334,13 @@ private static BoundCall CreateParameterlessCall(CSharpSyntaxNode syntax, BoundE | |||
internal sealed class AsyncForwardEntryPoint : SynthesizedEntryPointSymbol | |||
{ | |||
// The user-defined asyncrhonous main method. | |||
private MethodSymbol _userMain; | |||
private readonly MethodSymbol _userMain; | |||
private DiagnosticBag _diagnosticBag; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be readonly as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we figured out my earlier questionts about this bag in general? It seems like it's opening the door for duplicate / unnecessary errors here. Feel like they should be resolved at the point of creating this symbol, not later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've moved this bag to the constructor and provide the userMain location as the location for the diagnostic to go.
} | ||
return returnType.IsGenericTaskType(compilation) | ||
? compilation.GetSpecialType(SpecialType.System_Int32) | ||
: compilation.GetSpecialType(SpecialType.System_Void); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible for GetSpecialType
to return null
here or are we sufficiently protected by this point? If it can return null then we need to account for that as the return here is used in places where null
isn't acceptable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be protected at this point, as this code is only executed if an async-main entrypoint was found, which can't happen if GetSpecialType is broken.
{ | ||
if ((object)location == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like your formatting is off here. I heard there was this neat tool that formatted the code you changed in a git commit 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, the user still needs to remember to use the tool...
var userMainLocation = userMain.DeclaringSyntaxReferences.SingleOrDefault()?.GetLocation() ?? NoLocation.Singleton; | ||
|
||
_getAwaiterMethod = GetRequiredMethod(_userMain.ReturnType, WellKnownMemberNames.GetAwaiter, diagnosticBag, userMainLocation); | ||
_getResultMethod = GetRequiredMethod(_getAwaiterMethod.ReturnType, WellKnownMemberNames.GetResult, diagnosticBag); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When GetRequiredMethod fails does it return null
or does it fake a symbol? Curious to know what happens if this fails and then CreateBody is called (if that is possible).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It returns null.
CreateBody is not called if there are any error diagnostics in the bag. (GetRequiredMethod adds error diagnostics if it can't find anything).
BoundCall getAwaiterGetResult = | ||
CreateParameterlessCall( | ||
syntax: syntax, | ||
method: _getResultMethod, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can CreateBody be called if the constructor for AsyncForwardEntryPoint added an error to the diagnostic bag? If so this would end up passing a null
value for _getResultMethod
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CreateBody
is called from MethodCompiler.GetEntryPoint only if there are no error diagnostics.
@jaredpar: I think I've addressed everything that you've mentioned. #Resolved |
@@ -2059,28 +2059,31 @@ private AssemblySymbol GetForwardedToAssembly(string fullName, int arity, Diagno | |||
return null; | |||
} | |||
|
|||
internal static void CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag diagnostics, Location locationOpt = null) | |||
internal static bool CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag diagnostics, Location locationOpt = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like the return value is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yep, not used anymore.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
} | ||
[Fact] | ||
|
||
public void MainCanReturnTaskAndGenericOnInt_NoAsync() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public void MainCanReturnTaskAndGenericOnInt_NoAsync() [](start = 8, length = 54)
There is an empty line after the [Fact] attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
if (expectedOutput.Trim() != actualOutput.Trim()) | ||
if (expectedOutput.Trim() != Output.Trim()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (expectedOutput.Trim() != Output.Trim()) [](start = 12, length = 43)
Is this line safe for expectedOutput == null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
internal static CSharpSyntaxNode ExtractReturnTypeSyntax(this MethodSymbol method) | ||
{ | ||
method = method.PartialDefinitionPart ?? method; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make sure there are tests for partial methods:
- there is only definition
- there is only implementation
- definition is syntactically first
- implementation is syntactically first
Result produced by this method should be observable in the tests, probably through location of an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial methods must be void returning, so they won't hit any of these new code paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed offline, let's add direct tests for this function then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AlekseyTs: I've added tests for this API on non-main methods.
Done with review pass (iteration 52) |
|
||
|
||
var syntax = method.ExtractReturnTypeSyntax(); | ||
var dumbInstance = new BoundLiteral(syntax, ConstantValue.Null, method.ReturnType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BoundDefaultExpression(syntax, method.ReturnType)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, swore I fixed this earlier.
Done.
if (method.RefKind != RefKind.None) | ||
{ | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicated case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
LGTM |
} | ||
|
||
var syntax = method.ExtractReturnTypeSyntax(); | ||
var dumbInstance = new BoundLiteral(syntax, ConstantValue.Null, method.ReturnType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var dumbInstance = new BoundLiteral(syntax, ConstantValue.Null, method.ReturnType); [](start = 12, length = 83)
Were you going to change this to BoundDefaultExpression?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was, but the synthesized expression was emitting warnings about using "dot" after default, so I reverted it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
Done with review pass (iteration 54) |
Assert.Equal(1, m.Length); | ||
var returnSyntax = ((MethodSymbol) m.First()).ExtractReturnTypeSyntax(); | ||
var newLineCount = Environment.NewLine.Length * 1; | ||
Assert.Equal(returnSyntax.Location.SourceSpan, TextSpan.FromBounds(34 + newLineCount, 38 + newLineCount)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert.Equal(returnSyntax.Location.SourceSpan, TextSpan.FromBounds(34 + newLineCount, 38 + newLineCount)); [](start = 12, length = 106)
I think it would be better to find the node that you expect directly from the syntax tree. This way I cannot really tell if your expectations are correct. Also, I believe expected value should be first, otherwise failure message is confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once you find the node, simply assert that the nodes are the same.
In reply to: 113774955 [](ancestors = 113774955)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This applies to other new tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, and Done
var comp = CreateStandardCompilation(text); | ||
var global = comp.GlobalNamespace; | ||
var a = global.GetTypeMembers("A", 0).Single(); | ||
var m = a.GetMembers("M"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var m = a.GetMembers("M"); [](start = 12, length = 26)
I assume this is definition, please assert that for clarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -203,6 +204,84 @@ public partial class A { | |||
} | |||
|
|||
[Fact] | |||
public void PartialExtractSyntaxLocation_DeclBeforeDef() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PartialExtractSyntaxLocation_DeclBeforeDef [](start = 20, length = 42)
Please also test what the API returns when called off of implementation.
} | ||
|
||
[Fact] | ||
public void PartialExtractSyntaxLocation_DefBeforeDecl() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PartialExtractSyntaxLocation_DefBeforeDecl [](start = 20, length = 42)
Please also test what the API returns when called off of implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can I use the API to get the non-declaration method symbol?
Done with review pass (iteration 56) |
@@ -72,23 +72,18 @@ public CoreCLRRuntimeEnvironment(IEnumerable<ModuleData> additionalDependencies | |||
} | |||
} | |||
|
|||
public (int ExitCode, string Output) Execute(string moduleName, int expectedOutputLength) | |||
public int Execute(string moduleName, string[] args, string expectedOutput) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like args
is just being dropped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
errorOutputWriter = new CappedStringWriter(exLength); | ||
outputWriter = new CappedStringWriter(exLength); | ||
} | ||
else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just because there's no expected output doesn't mean we shouldn't capture the incorrect output. I would keep always using CappedStringWriter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CappedStringWriter fails if the capacity is exceeded, if you don't fill out expectedOutput, then it would be like saying "this program can not produce output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole file has been reverted.
@@ -11,25 +11,35 @@ internal static class SharedConsole | |||
private static TextWriter s_savedConsoleOut; | |||
private static TextWriter s_savedConsoleError; | |||
|
|||
private static AsyncLocal<StringWriter> s_currentOut; | |||
private static AsyncLocal<StringWriter> s_currentError; | |||
private static AsyncLocal<TextWriter> s_currentOut; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given my comment below this can stay as StringWriter
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole file has been reverted.
@@ -95,7 +95,7 @@ private Assembly LoadImageAsAssembly(ImmutableArray<byte> mainImage) | |||
} | |||
} | |||
|
|||
internal (int ExitCode, string Output) Execute(ImmutableArray<byte> mainImage, int expectedOutputLength) | |||
internal (int ExitCode, string Output) Execute(ImmutableArray<byte> mainImage, int? expectedOutputLength) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a reason for the int?
. If they don't expect output, just put in 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0 would mean "this program must produce no output"
{ | ||
try | ||
{ | ||
var emitData = GetEmitData(); | ||
emitData.RuntimeData.ExecuteRequested = true; | ||
return emitData.Manager.Execute(moduleName, expectedOutputLength, out processOutput); | ||
var resultCode = emitData.Manager.Execute(moduleName, args, expectedOutput?.Length, out var output); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would just pass expectedOutput?.Length ?? 0
and keep the arg as a non-nullable int
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole file has been reverted.
@@ -377,10 +371,20 @@ CompilationTestData IInternalRuntimeEnvironment.GetCompilationTestData() | |||
|
|||
private static readonly object s_consoleGuard = new object(); | |||
|
|||
internal static void Capture(Action action, int expectedLength, out string output, out string errorOutput) | |||
internal static void Capture(Action action, int? expectedLength, out string output, out string errorOutput) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need the int?
. The comparison doesn't happen until the top level and that helper can use expectedOutput == null
as a sentinel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed and defaulted with 0.
@@ -136,6 +142,8 @@ public abstract partial class CommonTestBase : TestBase | |||
manifestResources, | |||
expectedSignatures, | |||
expectedOutput, | |||
expectedReturnCode, | |||
args ?? new string[] { }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Array.Empty<string>()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
var node = tree.GetRoot().FindNode(token.Span); | ||
|
||
var mLocations = tree.GetRoot().DescendantTokens().Where(t => t.ValueText == "M").ToList(); | ||
var otherSymbol = (MethodSymbol)model.GetDeclaredSymbolForNode(tree.GetRoot().FindNode(mLocations[0].Span)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var otherSymbol = (MethodSymbol)model.GetDeclaredSymbolForNode(tree.GetRoot().FindNode(mLocations[0].Span)); [](start = 12, length = 108)
This is way too complicated than it should be.
var tree = comp.SyntaxTrees.Single(); | ||
var model = comp.GetSemanticModel(tree); | ||
var token = tree.GetRoot().DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).Skip(1).First(); | ||
var node = tree.GetRoot().FindNode(token.Span); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var node = tree.GetRoot().FindNode(token.Span); [](start = 12, length = 47)
This is way too complicated than it should be.
No description provided.