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
14 changes: 14 additions & 0 deletions src/Core/CodeAnalysis/Binding/ExpressionBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,20 @@ private static bool TryGetTaskElementType(TypeSymbol type, [NotNullWhen(true)] o
{
element = null;

// ADR-0172: an imported awaitable whose signature carries reference-
// nullability metadata arrives wrapped in a
// NullabilityAnnotatedTypeSymbol, hiding the symbolic constructed
// base from the fast path below — and the CLR fallback would erase a
// named-tuple element's names (they share the unnamed CLR backing).
// Unwrap to the symbolic base exactly when names are at stake; the
// base's arguments already carry their element nullability.
if (type is NullabilityAnnotatedTypeSymbol annotatedAwaitable
&& annotatedAwaitable.BaseType is ImportedTypeSymbol { TypeArguments.IsDefaultOrEmpty: false } symbolicAwaitable
&& symbolicAwaitable.TypeArguments.Any(TypeSymbol.ContainsNamedTupleElements))
{
type = symbolicAwaitable;
}

// Issue #2195: for an imported generic awaitable (e.g. `Task[T]`,
// `ValueTask[T]`) recover the awaited ELEMENT type from the SYMBOLIC
// type argument rather than the awaiter's CLR `GetResult()` return
Expand Down
55 changes: 53 additions & 2 deletions test/Compiler.Tests/Emit/NamedTupleMetadataEmitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,57 @@ import NamedLib
Assert.Equal($"3{Environment.NewLine}5{Environment.NewLine}3{Environment.NewLine}", output);
}

[Fact]
public void GsToGsRoundTrip_AwaitedTaskOfNamedTuple_KeepsNames()
{
// The imported awaitable arrives wrapped in nullability metadata
// (NullabilityAnnotatedTypeSymbol); the await element derivation must
// unwrap to the symbolic Task so `t.line` still binds — and the
// nullable named tuple (`(line int32, gitRef string?)?`) must survive
// an if-let unwrap of the awaited value.
var libSource = """
package NamedAsyncLib
import System.Threading.Tasks

class Fetcher {
shared {
async func Find() (line int32, column int32) {
await Task.Delay(1)
return (3, 5)
}

async func TryFind(ok bool) (line int32, gitRef string?)? {
await Task.Delay(1)
if ok {
return (7, "main")
}

return nil
}
}
}
""";
var appSource = """
package App
import System
import NamedAsyncLib

async func run() {
let t = await Fetcher.Find()
Console.WriteLine(t.line)
if let r = await Fetcher.TryFind(true) {
Console.WriteLine(r.line)
Console.WriteLine(r.gitRef)
}
}

run().Wait()
""";

var output = CompileAndRunWithLibrary(libSource, appSource, libraryName: "NamedAsyncLib");
Assert.Equal($"3{Environment.NewLine}7{Environment.NewLine}main{Environment.NewLine}", output);
}

private static string[] ReadNames(ICustomAttributeProvider provider)
{
var data = FindAttribute(provider);
Expand Down Expand Up @@ -205,13 +256,13 @@ private static Assembly CompileToAssembly(string source)
return Assembly.Load(File.ReadAllBytes(outPath));
}

private static string CompileAndRunWithLibrary(string libSource, string appSource)
private static string CompileAndRunWithLibrary(string libSource, string appSource, string libraryName = "namedlib")
{
var tempDir = Directory.CreateTempSubdirectory("gs_ntuple_rt_").FullName;
try
{
var libSrc = Path.Combine(tempDir, "lib.gs");
var libDll = Path.Combine(tempDir, "namedlib.dll");
var libDll = Path.Combine(tempDir, libraryName + ".dll");
var appSrc = Path.Combine(tempDir, "app.gs");
var appDll = Path.Combine(tempDir, "app.dll");
File.WriteAllText(libSrc, libSource);
Expand Down
Loading