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

Merge release/dev17.0-vs-deps to main-vs-deps #56919

Merged
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
94 changes: 91 additions & 3 deletions src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3267,6 +3267,7 @@ static void Main()
CompileAndVerify(source, expectedOutput: expectedOutput);
}

[WorkItem(56623, "https://github.com/dotnet/roslyn/issues/56623")]
[Fact]
public void OverloadResolution_16()
{
Expand All @@ -3277,21 +3278,47 @@ class Program
static void F(Func<Func<object>> f, int i) => Report(f);
static void F(Func<Func<int>> f, object o) => Report(f);
static void Main()
{
M(false);
}
static void M(bool b)
{
F(() => () => 1, 2);
F(() => () => { if (b) return 0; return 1; }, 2);
F(() => { if (b) return () => 0; return () => 1; }, 2);
}
static void Report(Delegate d) => Console.WriteLine(d.GetType());
}";

CompileAndVerify(source, parseOptions: TestOptions.Regular9, expectedOutput:
@"System.Func`1[System.Func`1[System.Object]]");
@"System.Func`1[System.Func`1[System.Object]]
System.Func`1[System.Func`1[System.Object]]
System.Func`1[System.Func`1[System.Object]]
");

// Breaking change from C#9 which binds calls to F(Func<Func<object>>, int).
//
// The calls such as F(() => () => 1, 2) should be considered ambiguous in C#9 as per the C# spec.
// But for compatibility with the legacy compiler, the implementation of "better conversion
// from expression" ignores delegate types for certain cases when the corresponding
// argument is a lambda expression, such as when the inferred return type of the lambda
// expression is null (see OverloadResolution.CanDowngradeConversionFromLambdaToNeither()).
// With the code example above, in C#10, the inferred return type of lambdas such as () => () => 1
// is Func<int> rather than null so the compatibility exception no longer applies for this example.
//
// We've decided to take the breaking change and match the C# spec rather than the
// legacy compiler in this particular case.
var expectedDiagnostics = new[]
{
// (8,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(Func<Func<object>>, int)' and 'Program.F(Func<Func<int>>, object)'
// (12,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(Func<Func<object>>, int)' and 'Program.F(Func<Func<int>>, object)'
// F(() => () => 1, 2);
Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(System.Func<System.Func<object>>, int)", "Program.F(System.Func<System.Func<int>>, object)").WithLocation(8, 9)
Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(System.Func<System.Func<object>>, int)", "Program.F(System.Func<System.Func<int>>, object)").WithLocation(12, 9),
// (13,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(Func<Func<object>>, int)' and 'Program.F(Func<Func<int>>, object)'
// F(() => () => { if (b) return 0; return 1; }, 2);
Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(System.Func<System.Func<object>>, int)", "Program.F(System.Func<System.Func<int>>, object)").WithLocation(13, 9),
// (14,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(Func<Func<object>>, int)' and 'Program.F(Func<Func<int>>, object)'
// F(() => { if (b) return () => 0; return () => 1; }, 2);
Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(System.Func<System.Func<object>>, int)", "Program.F(System.Func<System.Func<int>>, object)").WithLocation(14, 9)
};
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10);
comp.VerifyDiagnostics(expectedDiagnostics);
Expand Down Expand Up @@ -4218,6 +4245,67 @@ static void Main()
CompileAndVerify(source, expectedOutput: expectedOutput);
}

[Fact]
public void OverloadResolution_46()
{
var source =
@"using System;
class Program
{
static void F(Func<Func<object>> f, int i) => Report(f);
static void F(Func<Func<int>> f, object o) => Report(f);
static void Main()
{
M(false);
}
static void M(bool b)
{
F(() => b ? () => 0 : () => 1, 2);
}
static void Report(Delegate d) => Console.WriteLine(d.GetType());
}";

var expectedDiagnostics = new[]
{
// (12,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(Func<Func<object>>, int)' and 'Program.F(Func<Func<int>>, object)'
// F(() => b ? () => 0 : () => 1, 2);
Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(System.Func<System.Func<object>>, int)", "Program.F(System.Func<System.Func<int>>, object)").WithLocation(12, 9)
};
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9);
comp.VerifyDiagnostics(expectedDiagnostics);
comp = CreateCompilation(source, parseOptions: TestOptions.Regular10);
comp.VerifyDiagnostics(expectedDiagnostics);
comp = CreateCompilation(source);
comp.VerifyDiagnostics(expectedDiagnostics);
}

[Fact]
public void OverloadResolution_47()
{
var source =
@"using System;
class Program
{
static void F(int i, Func<Func<object>> f) => Report(f);
static void F(object o, Func<Func<int>> f) => Report(f);
static void Main()
{
F(2, () => new[] { () => 0, () => 1 }[0]);
}
static void Report(Delegate d) => Console.WriteLine(d.GetType());
}";

var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9);
comp.VerifyDiagnostics(
// (8,20): error CS0826: No best type found for implicitly-typed array
// F(2, () => new[] { () => 0, () => 1 }[0]);
Diagnostic(ErrorCode.ERR_ImplicitlyTypedArrayNoBestType, "new[] { () => 0, () => 1 }").WithLocation(8, 20));

var expectedOutput = @"System.Func`1[System.Func`1[System.Int32]]";
CompileAndVerify(source, parseOptions: TestOptions.Regular10, expectedOutput: expectedOutput);
CompileAndVerify(source, expectedOutput: expectedOutput);
}

[Fact]
public void BestCommonType_01()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2485,6 +2485,57 @@ public void InvalidGlobalLevelIsIgnored()
Assert.Equal(0, nonUserGlobalConfig.GlobalLevel);
}

[Theory]
[InlineData("/dir1/dir3/../dir2/file.cs", true)]
[InlineData("/dir1/./././././dir2/file.cs", true)]
[InlineData("/dir1/../dir1/../dir1/../dir1/dir2/file.cs", true)]
[InlineData("/dir1/dir3/dir4/../dir2/file.cs", false)]
[InlineData("file.cs", false)]
[InlineData("", false)]
[InlineData("/../../dir1/dir2/file.cs", true)]
[InlineData("/./../dir1/dir2/file.cs", true)]
[InlineData("/dir1/../../dir1/dir2/file.cs", true)]
[InlineData("/..", false)]
[InlineData("/../file.cs", false)]
[InlineData("/dir1/../file.cs", false)]
[InlineData("./dir1/dir2/file.cs", false)]
[InlineData("././../.././dir1/dir2/file.cs", false)]
[InlineData("./dir1/../file.cs", false)]
[InlineData("../dir1/dir2.cs", false)]
public void EquivalentSourcePathNames(string sourcePath, bool shouldMatch)
{
string sectionName = "/dir1/dir2/file.cs";

// append the drive root on windows (use something other than C: to ensure its not working by luck)
if (ExecutionConditionUtil.IsWindows)
{
sectionName = sectionName.Insert(0, "X:");
sourcePath = sourcePath.Insert(0, "X:");
}

var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse($@"
is_global = true
[{sectionName}]
a = b
", "/.editorconfig"));

var configSet = AnalyzerConfigSet.Create(configs, out var diagnostics);
configs.Free();

var options = configSet.GetOptionsForSourcePath(sourcePath);

if (shouldMatch)
{
Assert.Single(options.AnalyzerOptions);
Assert.Equal("b", options.AnalyzerOptions["a"]);
}
else
{
Assert.Empty(options.AnalyzerOptions);
}
}

#endregion
}
}
53 changes: 53 additions & 0 deletions src/Compilers/Core/CodeAnalysisTest/FileUtilitiesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,5 +268,58 @@ public void Extension()

Assert.Equal("*", PathUtilities.RemoveExtension("*.dll"));
}

[ConditionalTheory(typeof(WindowsOnly))]
[InlineData(@"x:\a\b\file.cs", "x:/a/b/file.cs")]
[InlineData(@"x:/a/b/file.cs", "x:/a/b/file.cs")]
[InlineData(@"x:\a\b\", "x:/a/b/")]
[InlineData(@"x:\a\b", "x:/a/b")]
[InlineData(@"x:\a\..\b\file.cs", "x:/b/file.cs")]
[InlineData(@"x:/a/../b/file.cs", "x:/b/file.cs")]
[InlineData(@"x:\a\b\..\c\", "x:/a/c/")]
[InlineData(@"x:\a\.\.\b\..\c\", "x:/a/c/")]
[InlineData(@"x:\..\..\a\.\.\b\..\c\", "x:/a/c/")]
[InlineData(@"\\server\a\b\", @"\\server\a\b\")]
[InlineData(@"\\server\..\a\.\.\b\..\c\", @"\\server\..\a\.\.\b\..\c\")]
[InlineData(@"x:a.cs", "x:a.cs")]
[InlineData(@"x:a//b//..//a.cs", "x:a//b//..//a.cs")]
[InlineData(@"ab:\c\d\", @"ab:\c\d\")]
[InlineData(@"ab:/c/d", @"ab:/c/d")]
[InlineData(@"ab:\c\..\d\", @"ab:\c\..\d\")]
[InlineData(@"\\.\C:\a\b.cs", @"\\.\C:\a\b.cs")]
[InlineData(@"\\.\C:\a\..\b.cs", @"\\.\C:\a\..\b.cs")]
[InlineData(@"\\?\C:\a\b.cs", @"\\?\C:\a\b.cs")]
[InlineData(@"\\.\Volume{00000000-0000-0000-0000-000000000000}\a\b.cs", @"\\.\Volume{00000000-0000-0000-0000-000000000000}\a\b.cs")]
[InlineData(@"\", @"\")]
[InlineData(@"\\", @"\\")]
[InlineData(@"\\server", @"\\server")]
[InlineData(@"x:", "x:")]
[InlineData(@"x:\", @"x:/")]
public void TestExpandAbsolutePathWithRelativeParts_Windows(string input, string expected) => TestExpandAbsolutePathWithRelativeParts(input, expected);

[Theory]
[InlineData("/", "/")]
[InlineData("/a/b/c", "/a/b/c")]
[InlineData("/a/b/c/file.cs", "/a/b/c/file.cs")]
[InlineData("/a/b/../c/file.cs", "/a/c/file.cs")]
[InlineData("/a/../b/../c/file.cs", "/c/file.cs")]
[InlineData("/../../c/file.cs", "/c/file.cs")]
[InlineData("/a/../b/./././../c/file.cs", "/c/file.cs")]
// not absolute paths
[InlineData("a/b/c", "a/b/c")]
[InlineData("./a/b/c", "./a/b/c")]
[InlineData("./a/../b/c", "./a/../b/c")]
[InlineData("../a/../b/c", "../a/../b/c")]
[InlineData("foo", "foo")]
[InlineData("", "")]
[InlineData(".", ".")]
[InlineData("..", "..")]
[InlineData("../", "../")]
[InlineData("./", "./")]
public void TestExpandAbsolutePathWithRelativeParts(string input, string expected)
{
var result = PathUtilities.ExpandAbsolutePathWithRelativeParts(input);
Assert.Equal(expected, result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath)
var sectionKey = _sectionKeyPool.Allocate();

var normalizedPath = PathUtilities.NormalizeWithForwardSlash(sourcePath);
normalizedPath = PathUtilities.ExpandAbsolutePathWithRelativeParts(normalizedPath);

// If we have a global config, add any sections that match the full path
foreach (var section in _globalConfig.NamedSections)
Expand Down
48 changes: 48 additions & 0 deletions src/Compilers/Core/Portable/FileSystem/PathUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.PooledObjects;

namespace Roslyn.Utilities
{
Expand Down Expand Up @@ -773,6 +775,52 @@ public static bool IsValidFilePath([NotNullWhen(true)] string? fullPath)
public static string NormalizeWithForwardSlash(string p)
=> DirectorySeparatorChar == '/' ? p : p.Replace(DirectorySeparatorChar, '/');

/// <summary>
/// Takes an absolute path and attempts to expand any '..' or '.' into their equivalent representation.
/// </summary>
/// <returns>An equivalent path that does not contain any '..' or '.' path parts, or the original path.</returns>
/// <remarks>
/// This method handles unix and windows drive rooted absolute paths only (i.e /a/b or x:\a\b). Passing any other kind of path
/// including relative, drive relative, unc, or windows device paths will simply return the original input.
/// </remarks>
public static string ExpandAbsolutePathWithRelativeParts(string p)
{
bool isDriveRooted = !IsUnixLikePlatform && IsDriveRootedAbsolutePath(p);
if (!isDriveRooted && !(p.Length > 1 && p[0] == AltDirectorySeparatorChar))
{
// if this isn't a regular absolute path we can't expand it correctly
return p;
}

// GetPathParts also removes any instances of '.'
var parts = GetPathParts(p);

// For drive rooted paths we need to skip the volume specifier, but remember it for re-joining later
var volumeSpecifier = isDriveRooted ? p.Substring(0, 2) : string.Empty;

// Skip the root directory
var toSkip = isDriveRooted ? 2 : 1;
Debug.Assert(parts[toSkip - 1] == string.Empty);

var resolvedParts = ArrayBuilder<string>.GetInstance();
foreach (var part in parts.Skip(toSkip))
{
if (!part.Equals(ParentRelativeDirectory))
{
resolvedParts.Push(part);
}
// /../../file is considered equal to /file, so we only process the parent relative directory info if there is actually a parent
else if (resolvedParts.Count > 0)
{
resolvedParts.Pop();
}
}

var expandedPath = volumeSpecifier + '/' + string.Join("/", resolvedParts);
resolvedParts.Free();
return expandedPath;
}

public static readonly IEqualityComparer<string> Comparer = new PathComparer();

private class PathComparer : IEqualityComparer<string?>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
<Compile Include="..\..\Compilers\Core\Portable\FileSystem\FileUtilities.cs" Link="Utilities\FileUtilities.cs" />
<Compile Include="..\..\Compilers\Core\Portable\FileSystem\PathUtilities.cs" Link="Utilities\PathUtilities.cs" />
<Compile Include="..\..\Compilers\Core\Portable\FileSystem\PathKind.cs" Link="Utilities\PathKind.cs" />
<Compile Include="..\..\Compilers\Core\Portable\Collections\ArrayBuilderExtensions.cs" Link="Utilities\ArrayBuilderExtensions.cs" />
<Compile Include="..\..\Dependencies\PooledObjects\ArrayBuilder.cs" Link="Utilities\ArrayBuilder.cs" />
<Compile Include="..\..\Dependencies\PooledObjects\ArrayBuilder.Enumerator.cs" Link="Utilities\ArrayBuilder.Enumerator.cs" />
<Compile Include="..\..\Dependencies\PooledObjects\ObjectPool`1.cs" Link="Utilities\ObjectPool`1.cs" />
<Compile Include="..\..\Dependencies\PooledObjects\PooledHashSet.cs" Link="Utilities\PooledHashSet.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StreamJsonRpc" Version="$(StreamJsonRpcVersion)" />
Expand Down