Skip to content

Commit

Permalink
Merge pull request #56919 from dotnet/merges/release/dev17.0-vs-deps-…
Browse files Browse the repository at this point in the history
…to-main-vs-deps

Merge release/dev17.0-vs-deps to main-vs-deps
  • Loading branch information
dotnet-bot authored Oct 1, 2021
2 parents e420900 + a4187cc commit 8475925
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 3 deletions.
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

0 comments on commit 8475925

Please sign in to comment.