Skip to content

Commit

Permalink
Merge branch 'draft-v8' into nullable-type-definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
BillWagner authored Oct 7, 2024
2 parents f09154c + 0e44170 commit 0a03e8e
Show file tree
Hide file tree
Showing 16 changed files with 108 additions and 90 deletions.
1 change: 1 addition & 0 deletions .github/workflows/renumber-sections.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
env:
DOTNET_NOLOGO: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/test-examples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ on:
jobs:
test-extraction-and-runner:
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
env:
DOTNET_NOLOGO: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}

steps:
- name: Check out our repo
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/word-converter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
env:
DOTNET_NOLOGO: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -361,5 +361,5 @@ test-grammar/
# don't checkin jar files:
*.jar

# Don't checkin debug settings.
# don't checkin launchSettings:
**/launchSettings.json
5 changes: 2 additions & 3 deletions standard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,8 @@
- [§10.3.5](conversions.md#1035-explicit-reference-conversions) Explicit reference conversions
- [§10.3.6](conversions.md#1036-explicit-tuple-conversions) Explicit tuple conversions
- [§10.3.7](conversions.md#1037-unboxing-conversions) Unboxing conversions
- [§10.3.8](conversions.md#1038-explicit-dynamic-conversions) Explicit dynamic conversions
- [§10.3.9](conversions.md#1039-explicit-conversions-involving-type-parameters) Explicit conversions involving type parameters
- [§10.3.10](conversions.md#10310-user-defined-explicit-conversions) User-defined explicit conversions
- [§10.3.8](conversions.md#1038-explicit-conversions-involving-type-parameters) Explicit conversions involving type parameters
- [§10.3.9](conversions.md#1039-user-defined-explicit-conversions) User-defined explicit conversions
- [§10.4](conversions.md#104-standard-conversions) Standard conversions
- [§10.4.1](conversions.md#1041-general) General
- [§10.4.2](conversions.md#1042-standard-implicit-conversions) Standard implicit conversions
Expand Down
58 changes: 8 additions & 50 deletions standard/conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The pre-defined implicit conversions always succeed and never cause exceptions t
For the purposes of conversion, the types `object` and `dynamic` are identity convertible ([§10.2.2](conversions.md#1022-identity-conversion)).
However, dynamic conversions ([§10.2.10](conversions.md#10210-implicit-dynamic-conversions) and10.3.8](conversions.md#1038-explicit-dynamic-conversions)) apply only to expressions of type `dynamic` ([§8.2.4](types.md#824-the-dynamic-type)).
However, dynamic conversions ([§10.2.10](conversions.md#10210-implicit-dynamic-conversions)) apply only to expressions of type `dynamic` ([§8.2.4](types.md#824-the-dynamic-type)).
### 10.2.2 Identity conversion
Expand Down Expand Up @@ -394,9 +394,8 @@ The following conversions are classified as explicit conversions:
- Explicit reference conversions ([§10.3.5](conversions.md#1035-explicit-reference-conversions))
- Explicit interface conversions
- Unboxing conversions ([§10.3.7](conversions.md#1037-unboxing-conversions))
- Explicit type parameter conversions ([§10.3.9](conversions.md#1039-explicit-conversions-involving-type-parameters))
- Explicit dynamic conversions ([§10.3.8](conversions.md#1038-explicit-dynamic-conversions))
- User-defined explicit conversions ([§10.3.10](conversions.md#10310-user-defined-explicit-conversions))
- Explicit type parameter conversions ([§10.3.8](conversions.md#1038-explicit-conversions-involving-type-parameters))
- User-defined explicit conversions ([§10.3.9](conversions.md#1039-user-defined-explicit-conversions))
Explicit conversions can occur in cast expressions ([§12.9.7](expressions.md#1297-cast-expressions)).
Expand Down Expand Up @@ -471,7 +470,7 @@ The explicit nullable conversions are those nullable conversions ([§10.6.1](con
The explicit reference conversions are:
- From object and dynamic to any other *reference_type*.
- From object to any other *reference_type*.
- From any *class_type* `S` to any *class_type* `T`, provided `S` is a base class of `T`.
- From any *class_type* `S` to any *interface_type* `T`, provided `S` is not sealed and provided `S` does not implement `T`.
- From any *interface_type* `S` to any *class_type* `T`, provided `T` is not sealed or provided `T` implements `S`.
Expand All @@ -489,7 +488,7 @@ The explicit reference conversions are:
- If `Xᵢ` is invariant, then `Sᵢ` is identical to `Tᵢ`.
- If `Xᵢ` is covariant, then there is an identity conversion, implicit reference conversion or explicit reference conversion from `Sᵢ` to `Tᵢ`.
- If `Xᵢ` is contravariant, then `Sᵢ` and `Tᵢ` are either identical or both reference types.
- Explicit conversions involving type parameters that are known to be reference types. For more details on explicit conversions involving type parameters, see [§10.3.9](conversions.md#1039-explicit-conversions-involving-type-parameters).
- Explicit conversions involving type parameters that are known to be reference types. For more details on explicit conversions involving type parameters, see [§10.3.8](conversions.md#1038-explicit-conversions-involving-type-parameters).
The explicit reference conversions are those conversions between *reference_type*s that require run-time checks to ensure they are correct.
Expand All @@ -512,7 +511,7 @@ An unboxing conversion permits a *reference_type* to be explicitly converted to
- From any *interface_type* `I` to any *non_nullable_value_type* where there is an unboxing conversion from an *interface_type* `I₀` to the *non_nullable_value-type* and an identity conversion from `I` to `I₀`.
- From any *interface_type* `I` to any *non_nullable_value_type* where there is an unboxing conversion from an *interface_type* `I₀` to the *non_nullable_value_type* and either either `I₀` is variance_convertible to `I` or `I` is variance-convertible to `I₀` ([§18.2.3.3](interfaces.md#18233-variance-conversion)).
- From any *reference_type* to any *nullable_value_type* where there is an unboxing conversion from *reference_type* to the underlying *non_nullable_value_type* of the *nullable_value_type*.
- From a type parameter which is not known to be a value type to any type such that the conversion is permitted by [§10.3.9](conversions.md#1039-explicit-conversions-involving-type-parameters).
- From a type parameter which is not known to be a value type to any type such that the conversion is permitted by [§10.3.8](conversions.md#1038-explicit-conversions-involving-type-parameters).
An unboxing operation to a *non_nullable_value_type* consists of first checking that the object instance is a boxed value of the given *non_nullable_value_type*, and then copying the value out of the instance.
Expand Down Expand Up @@ -540,48 +539,7 @@ For an unboxing conversion to a given *non_nullable_value_type* to succeed at ru
For an unboxing conversion to a given *nullable_value_type* to succeed at run-time, the value of the source operand shall be either null or a reference to a boxed value of the underlying *non_nullable_value_type* of the *nullable_value_type*. If the source operand is a reference to an incompatible object, a `System.InvalidCastException` is thrown.
### 10.3.8 Explicit dynamic conversions
An explicit dynamic conversion exists from an expression of type `dynamic` to any type `T`. The conversion is dynamically bound ([§12.3.3](expressions.md#1233-dynamic-binding)), which means that an explicit conversion will be sought at run-time from the run-time type of the expression to `T`. If no conversion is found, a run-time exception is thrown.
If dynamic binding of the conversion is not desired, the expression can be first converted to `object`, and then to the desired type.
> *Example*: Assume the following class is defined:
>
> <!-- Example: {template:"standalone-lib", name:"ExplicitDynamic1"} -->
> <!-- Maintenance Note: A version of this type exists in additional-files as "CForConversions.cs". As such, certain changes to this type definition might need to be reflected in that file, in which case, *all* examples using that file should be tested. -->
> ```csharp
> class C
> {
> int i;
>
> public C(int i)
> {
> this.i = i;
> }
>
> public static explicit operator C(string s)
> {
> return new C(int.Parse(s));
> }
> }
> ```
>
> The following illustrates explicit dynamic conversions:
>
> <!-- Example: {template:"standalone-console-without-using", name:"ExplicitDynamic2", expectedException:"InvalidCastException", additionalFiles:["CForConversions.cs"]} -->
> ```csharp
> object o = "1";
> dynamic d = "2";
> var c1 = (C)o; // Compiles, but explicit reference conversion fails
> var c2 = (C)d; // Compiles and user defined conversion succeeds
> ```
>
> The best conversion of `o` to `C` is found at compile-time to be an explicit reference conversion. This fails at run-time, because `"1"` is not in fact a `C`. The conversion of `d` to `C` however, as an explicit dynamic conversion, is suspended to run-time, where a user defined conversion from the run-time type of `d` (`string`) to `C` is found, and succeeds.
>
> *end example*
### 10.3.9 Explicit conversions involving type parameters
### 10.3.8 Explicit conversions involving type parameters
For a *type_parameter* `T` that is known to be a reference type ([§15.2.5](classes.md#1525-type-parameter-constraints)), the following explicit reference conversions ([§10.3.5](conversions.md#1035-explicit-reference-conversions)) exist:
Expand Down Expand Up @@ -636,7 +594,7 @@ The above rules do not permit a direct explicit conversion from an unconstrained
>
> *end example*
### 10.3.10 User-defined explicit conversions
### 10.3.9 User-defined explicit conversions
A user-defined explicit conversion consists of an optional standard explicit conversion, followed by execution of a user-defined implicit or explicit conversion operator, followed by another optional standard explicit conversion. The exact rules for evaluating user-defined explicit conversions are described in10.5.5](conversions.md#1055-user-defined-explicit-conversions).
Expand Down
2 changes: 1 addition & 1 deletion standard/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ Since a type parameter can be instantiated with many different type arguments, t
>
> - A type parameter cannot be used directly to declare a base class ([§15.2.4.2](classes.md#15242-base-classes)) or interface ([§18.2.4](interfaces.md#1824-base-interfaces)).
> - The rules for member lookup on type parameters depend on the constraints, if any, applied to the type parameter. They are detailed in [§12.5](expressions.md#125-member-lookup).
> - The available conversions for a type parameter depend on the constraints, if any, applied to the type parameter. They are detailed in [§10.2.12](conversions.md#10212-implicit-conversions-involving-type-parameters) and [§10.3.9](conversions.md#1039-explicit-conversions-involving-type-parameters).
> - The available conversions for a type parameter depend on the constraints, if any, applied to the type parameter. They are detailed in [§10.2.12](conversions.md#10212-implicit-conversions-involving-type-parameters) and [§10.3.8](conversions.md#1038-explicit-conversions-involving-type-parameters).
> - The literal `null` cannot be converted to a type given by a type parameter, except if the type parameter is known to be a reference type ([§10.2.12](conversions.md#10212-implicit-conversions-involving-type-parameters)). However, a default expression ([§12.8.20](expressions.md#12820-default-value-expressions)) can be used instead. In addition, a value with a type given by a type parameter *can* be compared with null using `==` and `!=` ([§12.12.7](expressions.md#12127-reference-type-equality-operators)) unless the type parameter has the value type constraint.
> - A `new` expression ([§12.8.16.2](expressions.md#128162-object-creation-expressions)) can only be used with a type parameter if the type parameter is constrained by a *constructor_constraint* or the value type constraint ([§15.2.5](classes.md#1525-type-parameter-constraints)).
> - A type parameter cannot be used anywhere within an attribute.
Expand Down
1 change: 1 addition & 0 deletions tools/ExampleTester/ExampleTester.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<ItemGroup>
<ProjectReference Include="..\ExampleExtractor\ExampleExtractor.csproj" />
<ProjectReference Include="..\Utilities\Utilities.csproj" />
</ItemGroup>

</Project>
35 changes: 17 additions & 18 deletions tools/ExampleTester/GeneratedExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Newtonsoft.Json;
using System.Reflection;
using System.Text;
using Utilities;

namespace ExampleTester;

Expand Down Expand Up @@ -33,9 +34,9 @@ private static GeneratedExample Load(string directory)
return new GeneratedExample(directory);
}

internal async Task<bool> Test(TesterConfiguration configuration)
internal async Task<bool> Test(TesterConfiguration configuration, StatusCheckLogger logger)
{
var outputLines = new List<string> { $"Testing {Metadata.Name} from {Metadata.Source}" };
logger.ConsoleOnlyLog(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $"Testing {Metadata.Name} from {Metadata.Source}", "ExampleTester");

// Explicitly do a release build, to avoid implicitly defining DEBUG.
var properties = new Dictionary<string, string> { { "Configuration", "Release" } };
Expand All @@ -52,21 +53,16 @@ internal async Task<bool> Test(TesterConfiguration configuration)
}

bool ret = true;
ret &= ValidateDiagnostics("errors", DiagnosticSeverity.Error, Metadata.ExpectedErrors);
ret &= ValidateDiagnostics("warnings", DiagnosticSeverity.Warning, Metadata.ExpectedWarnings, Metadata.IgnoredWarnings);
ret &= ValidateDiagnostics("errors", DiagnosticSeverity.Error, Metadata.ExpectedErrors, logger);
ret &= ValidateDiagnostics("warnings", DiagnosticSeverity.Warning, Metadata.ExpectedWarnings, logger, Metadata.IgnoredWarnings);
// Don't try to validate output if we've already failed in terms of errors and warnings, or if we expect errors.
if (ret && Metadata.ExpectedErrors is null)
{
ret &= ValidateOutput();
}

if (!ret || !configuration.Quiet)
{
outputLines.ForEach(Console.WriteLine);
}
return ret;

bool ValidateDiagnostics(string type, DiagnosticSeverity severity, List<string> expected, List<string>? ignored = null)
bool ValidateDiagnostics(string type, DiagnosticSeverity severity, List<string> expected, StatusCheckLogger logger, List<string>? ignored = null)
{
expected ??= new List<string>();
ignored ??= new List<string>();
Expand All @@ -81,10 +77,12 @@ bool ValidateDiagnostics(string type, DiagnosticSeverity severity, List<string>
bool ret = ValidateExpectedAgainstActual(type, expected, actualIds);
if (!ret)
{
outputLines.Add($" Details of actual {type}:");
logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Details of actual {type}:", "ExampleTester");
foreach (var diagnostic in actualDiagnostics)
{
outputLines.Add($" Line {diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1}: {diagnostic.Id}: {diagnostic.GetMessage()}");
logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine,
$" Line {diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1}: {diagnostic.Id}: {diagnostic.GetMessage()}",
"ExampleTester");
}
}
return ret;
Expand All @@ -97,7 +95,7 @@ bool ValidateOutput()
{
if (Metadata.ExpectedOutput != null)
{
outputLines.Add(" Output expected, but project has no entry point.");
logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, " Output expected, but project has no entry point.", "ExampleTester");
return false;
}
return true;
Expand All @@ -114,21 +112,21 @@ bool ValidateOutput()
var emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
outputLines.Add(" Failed to emit assembly");
logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, " Failed to emit assembly", "ExampleTester");
return false;
}

var generatedAssembly = Assembly.Load(ms.ToArray());
var type = generatedAssembly.GetType(typeName);
if (type is null)
{
outputLines.Add($" Failed to find entry point type {typeName}");
logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Failed to find entry point type {typeName}", "ExampleTester");
return false;
}
var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (method is null)
{
outputLines.Add($" Failed to find entry point method {typeName}.{methodName}");
logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Failed to find entry point method {typeName}.{methodName}", "ExampleTester");
return false;
}
var arguments = method.GetParameters().Any()
Expand Down Expand Up @@ -197,7 +195,7 @@ bool MaybeReportError(bool result, string message)
{
if (!result)
{
outputLines.Add(message);
logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, message, "ExampleTester");
}
return result;
}
Expand All @@ -207,7 +205,8 @@ bool ValidateExpectedAgainstActual(string type, List<string> expected, List<stri
{
if (!expected.SequenceEqual(actual))
{
outputLines.Add($" Mismatched {type}: Expected {string.Join(", ", expected)}; Was {string.Join(", ", actual)}");
logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine,
$" Mismatched {type}: Expected {string.Join(", ", expected)}; Was {string.Join(", ", actual)}", "ExampleTester");
return false;
}
return true;
Expand Down
17 changes: 14 additions & 3 deletions tools/ExampleTester/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
using ExampleTester;
using System.CommandLine;
using Utilities;

var logger = new StatusCheckLogger("..", "Example tester");
var headSha = Environment.GetEnvironmentVariable("HEAD_SHA");
var token = Environment.GetEnvironmentVariable("GH_TOKEN");

var rootCommand = new RootCommand();
new TesterConfigurationBinder().ConfigureCommand(rootCommand, ExecuteAsync);
return rootCommand.Invoke(args);
int exitCode = rootCommand.Invoke(args);

if ((token is not null) && (headSha is not null))
{
await logger.BuildCheckRunResult(token, "dotnet", "csharpstandard", headSha);
}
return exitCode;

async Task<int> ExecuteAsync(TesterConfiguration configuration)
{
Expand Down Expand Up @@ -31,7 +42,7 @@ async Task<int> ExecuteAsync(TesterConfiguration configuration)
foreach (var example in examples)
{
// The Run method explains any failures, we just need to count them.
if (!await example.Test(configuration))
if (!await example.Test(configuration, logger))
{
failures++;
}
Expand All @@ -42,4 +53,4 @@ async Task<int> ExecuteAsync(TesterConfiguration configuration)
Console.WriteLine($"Failures: {failures}");

return failures;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="XMLUnit.Core" Version="2.10.0" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
Loading

0 comments on commit 0a03e8e

Please sign in to comment.