Skip to content

Commit aabc675

Browse files
author
Stephan
committed
Prevent ambiguous type symbols in generated code [#59]
1 parent c15435e commit aabc675

File tree

4 files changed

+277
-11
lines changed

4 files changed

+277
-11
lines changed

Zbu.ModelsBuilder.Tests/BuilderTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,6 +1697,7 @@ public void GenerateMixinType()
16971697
public void WriteClrType(string expected, Type input)
16981698
{
16991699
var builder = new TextBuilder();
1700+
builder.ModelsNamespaceForTests = "ModelsNamespace";
17001701
var sb = new StringBuilder();
17011702
builder.WriteClrType(sb, input);
17021703
Assert.AreEqual(expected, sb.ToString());
@@ -1710,13 +1711,78 @@ public void WriteClrTypeUsing(string expected, Type input)
17101711
{
17111712
var builder = new TextBuilder();
17121713
builder.Using.Add("Zbu.ModelsBuilder.Tests");
1714+
builder.ModelsNamespaceForTests = "ModelsNamespace";
17131715
var sb = new StringBuilder();
17141716
builder.WriteClrType(sb, input);
17151717
Assert.AreEqual(expected, sb.ToString());
17161718
}
17171719

1720+
[Test]
1721+
public void WriteClrType_WithUsing()
1722+
{
1723+
var builder = new TextBuilder();
1724+
builder.Using.Add("System.Text");
1725+
builder.ModelsNamespaceForTests = "Zbu.ModelsBuilder.Tests.Models";
1726+
var sb = new StringBuilder();
1727+
builder.WriteClrType(sb, typeof(StringBuilder));
1728+
Assert.AreEqual("StringBuilder", sb.ToString());
1729+
}
1730+
1731+
[Test]
1732+
public void WriteClrTypeAnother_WithoutUsing()
1733+
{
1734+
var builder = new TextBuilder();
1735+
builder.ModelsNamespaceForTests = "Zbu.ModelsBuilder.Tests.Models";
1736+
var sb = new StringBuilder();
1737+
builder.WriteClrType(sb, typeof(StringBuilder));
1738+
Assert.AreEqual("System.Text.StringBuilder", sb.ToString());
1739+
}
1740+
1741+
[Test]
1742+
public void WriteClrType_Ambiguous()
1743+
{
1744+
var builder = new TextBuilder();
1745+
builder.Using.Add("System.Text");
1746+
builder.Using.Add("Zbu.ModelsBuilder.Tests");
1747+
builder.ModelsNamespaceForTests = "SomeRandomNamespace";
1748+
var sb = new StringBuilder();
1749+
builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding));
1750+
Assert.AreEqual("global::System.Text.ASCIIEncoding", sb.ToString());
1751+
}
1752+
1753+
[Test]
1754+
public void WriteClrType_AmbiguousNot()
1755+
{
1756+
var builder = new TextBuilder();
1757+
builder.Using.Add("System.Text");
1758+
builder.Using.Add("Zbu.ModelsBuilder.Tests");
1759+
builder.ModelsNamespaceForTests = "Zbu.ModelsBuilder.Tests.Models";
1760+
var sb = new StringBuilder();
1761+
builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding));
1762+
Assert.AreEqual("ASCIIEncoding", sb.ToString());
1763+
}
1764+
1765+
[Test]
1766+
public void WriteClrType_AmbiguousWithNested()
1767+
{
1768+
var builder = new TextBuilder();
1769+
builder.Using.Add("System.Text");
1770+
builder.Using.Add("Zbu.ModelsBuilder.Tests");
1771+
builder.ModelsNamespaceForTests = "SomeRandomNamespace";
1772+
var sb = new StringBuilder();
1773+
builder.WriteClrType(sb, typeof(ASCIIEncoding.Nested));
1774+
Assert.AreEqual("global::Zbu.ModelsBuilder.Tests.ASCIIEncoding.Nested", sb.ToString());
1775+
}
1776+
17181777
public class Class1 { }
17191778
}
17201779

1780+
// make it public to be ambiguous (see above)
1781+
public class ASCIIEncoding
1782+
{
1783+
// can we handle nested types?
1784+
public class Nested { }
1785+
}
1786+
17211787
class BuilderTestsClass1 {}
17221788
}

Zbu.ModelsBuilder.Tests/RoslynTests.cs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,150 @@ class MyClass
416416
Assert.AreEqual(0, diags.Length);
417417
// unknown attribute is a semantic error
418418
}
419+
420+
[Test]
421+
public void SymbolLookup_Ambiguous1()
422+
{
423+
const string code = @"
424+
using System.Text;
425+
using Zbu.ModelsBuilder.Tests;
426+
namespace SomeCryptoNameThatDoesReallyNotExistEgAGuid
427+
{ }
428+
";
429+
430+
SemanticModel model;
431+
int pos;
432+
GetSemantic(code, out model, out pos);
433+
434+
Assert.AreEqual(1, LookupSymbol(model, pos, null, "StringBuilder"));
435+
Assert.AreEqual(1, LookupSymbol(model, pos, null, "RoslynTests"));
436+
Assert.AreEqual(2, LookupSymbol(model, pos, null, "ASCIIEncoding"));
437+
}
438+
439+
public void SymbolLookup_Ambiguous2()
440+
{
441+
const string code = @"
442+
using System.Text;
443+
using Zbu.ModelsBuilder.Tests;
444+
namespace Zbu.ModelsBuilder.Tests.Models
445+
{ }
446+
";
447+
448+
SemanticModel model;
449+
int pos;
450+
GetSemantic(code, out model, out pos);
451+
452+
Assert.AreEqual(1, LookupSymbol(model, pos, null, "StringBuilder"));
453+
Assert.AreEqual(1, LookupSymbol(model, pos, null, "RoslynTests"));
454+
455+
// not ambiguous !
456+
Assert.AreEqual(1, LookupSymbol(model, pos, null, "ASCIIEncoding"));
457+
}
458+
459+
[Test]
460+
public void SymbolLookup()
461+
{
462+
const string code = @"
463+
using System.Collections.Generic;
464+
using System.Text;
465+
namespace MyNamespace
466+
{
467+
// %%POS%%
468+
469+
public class MyClass
470+
{
471+
public MyClass()
472+
{ }
473+
474+
public void Do()
475+
{ }
476+
}
477+
478+
public class OtherClass
479+
{
480+
public class NestedClass { }
481+
private class HiddenClass { }
482+
}
483+
}
484+
";
485+
SemanticModel model;
486+
int pos;
487+
GetSemantic(code, out model, out pos);
488+
489+
// only in proper scope
490+
Assert.AreEqual(1, LookupSymbol(model, pos, null, "MyClass"));
491+
Assert.AreEqual(0, LookupSymbol(model, 0, null, "MyClass"));
492+
493+
// not! looking for symbols only
494+
Assert.AreEqual(0, LookupSymbol(model, 0, null, "MyNamespace.MyClass"));
495+
Assert.AreEqual(0, LookupSymbol(model, pos, null, "MyNamespace.MyClass"));
496+
497+
// yes!
498+
Assert.AreEqual(1, LookupSymbol(model, 0, null, "StringBuilder"));
499+
Assert.AreEqual(1, LookupSymbol(model, pos, null, "StringBuilder"));
500+
501+
// not! looking for symbols only
502+
Assert.AreEqual(0, LookupSymbol(model, 0, null, "System.Text.StringBuilder"));
503+
Assert.AreEqual(0, LookupSymbol(model, 0, null, "global::StringBuilder"));
504+
Assert.AreEqual(0, LookupSymbol(model, 0, null, "global::System.Text.StringBuilder"));
505+
506+
// cannot find Int32 at root because of no using clause
507+
Assert.AreEqual(0, LookupSymbol(model, 0, null, "Int32"));
508+
509+
// can find System.Collections.Generic.Dictionary<TKey, TValue> (using clause)
510+
Assert.AreEqual(1, LookupSymbol(model, 0, null, "Dictionary"));
511+
512+
// can find OtherClass within MyNamespace, & nested...
513+
Assert.AreEqual(1, LookupSymbol(model, pos, null, "OtherClass"));
514+
515+
// that's not how it works
516+
//Assert.AreEqual(0, LookupSymbol(model, pos, null, "NestedClass"));
517+
//Assert.AreEqual(1, LookupSymbol(model, pos, null, "OtherClass.NestedClass"));
518+
//Assert.AreEqual(0, LookupSymbol(model, pos, null, "OtherClass+HiddenClass"));
519+
}
520+
521+
private void GetSemantic(string code, out SemanticModel model, out int pos)
522+
{
523+
var tree = CSharpSyntaxTree.ParseText(code);
524+
525+
//var writer = new ConsoleDumpWalker();
526+
//writer.Visit(tree.GetRoot());
527+
528+
var refs = AssemblyUtility.GetAllReferencedAssemblyLocations()
529+
.Distinct()
530+
.Select(x => MetadataReference.CreateFromFile(x));
531+
532+
var compilation = CSharpCompilation.Create(
533+
"MyCompilation",
534+
syntaxTrees: new[] { tree },
535+
references: refs);
536+
model = compilation.GetSemanticModel(tree);
537+
538+
var diags = model.GetDiagnostics();
539+
foreach (var diag in diags)
540+
{
541+
if (diag.Id == "CS8019") continue;
542+
Console.WriteLine(diag);
543+
Assert.Fail();
544+
}
545+
546+
var ns = tree.GetRoot().DescendantNodes().OfType<NamespaceDeclarationSyntax>().First();
547+
pos = ns.OpenBraceToken.SpanStart;
548+
549+
// if we use that as a "container" then we get what's in the container *only*
550+
// not what we want here, then we have to use the position to determine *scope*
551+
//var ss = model.GetDeclaredSymbol(ns);
552+
}
553+
554+
private int LookupSymbol(SemanticModel model, int pos, INamespaceOrTypeSymbol container, string symbol)
555+
{
556+
Console.WriteLine("lookup: " + symbol);
557+
//var symbols = model.LookupSymbols(0, container, symbol);
558+
var symbols = model.LookupNamespacesAndTypes(pos, container, symbol);
559+
foreach (var x in symbols)
560+
Console.WriteLine(x.ToDisplayString());
561+
return symbols.Length;
562+
}
419563
}
420564

421565
class TestWalker : CSharpSyntaxWalker

Zbu.ModelsBuilder/Building/Builder.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Text;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
48

59
namespace Zbu.ModelsBuilder.Building
610
{
@@ -211,8 +215,52 @@ private void Prepare()
211215
typeModel.HasCtor = true;
212216
}
213217

218+
private SemanticModel _ambiguousSymbolsModel;
219+
private int _ambiguousSymbolsPos;
220+
221+
// internal for tests
222+
internal void PrepareAmbiguousSymbols()
223+
{
224+
var codeBuilder = new StringBuilder();
225+
foreach (var t in TypesUsing)
226+
codeBuilder.AppendFormat("using {0};\n", t);
227+
228+
codeBuilder.AppendFormat("namespace {0}\n{{ }}\n", GetModelsNamespace());
229+
230+
var tree = CSharpSyntaxTree.ParseText(codeBuilder.ToString());
231+
232+
var refs = AssemblyUtility.GetAllReferencedAssemblyLocations()
233+
.Distinct() // else massively duplicated...
234+
.Select(x => MetadataReference.CreateFromFile(x));
235+
236+
var compilation = CSharpCompilation.Create(
237+
"MyCompilation",
238+
syntaxTrees: new[] { tree },
239+
references: refs);
240+
_ambiguousSymbolsModel = compilation.GetSemanticModel(tree);
241+
242+
var namespaceSyntax = tree.GetRoot().DescendantNodes().OfType<NamespaceDeclarationSyntax>().First();
243+
//var namespaceSymbol = model.GetDeclaredSymbol(namespaceSyntax);
244+
_ambiguousSymbolsPos = namespaceSyntax.OpenBraceToken.SpanStart;
245+
}
246+
247+
protected bool IsAmbiguousSymbol(string symbol)
248+
{
249+
if (_ambiguousSymbolsModel == null)
250+
PrepareAmbiguousSymbols();
251+
if (_ambiguousSymbolsModel == null)
252+
throw new Exception("Could not prepare ambiguous symbols.");
253+
var symbols = _ambiguousSymbolsModel.LookupNamespacesAndTypes(_ambiguousSymbolsPos, null, symbol);
254+
return symbols.Length > 1;
255+
}
256+
257+
internal string ModelsNamespaceForTests;
258+
214259
public string GetModelsNamespace()
215260
{
261+
if (ModelsNamespaceForTests != null)
262+
return ModelsNamespaceForTests;
263+
216264
// code attribute overrides everything
217265
if (ParseResult.HasModelsNamespace)
218266
return ParseResult.ModelsNamespace;

Zbu.ModelsBuilder/Building/TextBuilder.cs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -343,20 +343,28 @@ internal void WriteClrType(StringBuilder sb, Type type)
343343

344344
private void WriteNonGenericClrType(StringBuilder sb, string s)
345345
{
346-
var ls = s.ToLowerInvariant();
347-
if (TypesMap.ContainsKey(ls))
346+
string typeName;
347+
if (!TypesMap.TryGetValue(s.ToLowerInvariant(), out typeName)) // takes care eg of "System.Int32" vs. "int"
348348
{
349-
s = TypesMap[ls];
350-
}
351-
else
352-
{
353-
var p = s.LastIndexOf('.');
354-
if (p > 0 && TypesUsing.Contains(s.Substring(0, p)))
355-
s = s.Substring(p + 1);
356-
s = s.Replace("+", "."); // nested types *after* using
349+
// if full type name matches a using clause, strip
350+
typeName = s;
351+
var p = typeName.LastIndexOf('.');
352+
if (p > 0 && TypesUsing.Contains(typeName.Substring(0, p)))
353+
typeName = typeName.Substring(p + 1);
354+
355+
// nested types *after* using
356+
typeName = typeName.Replace("+", ".");
357+
358+
// symbol to test is the first part of the name
359+
p = typeName.IndexOf('.');
360+
var symbol = p > 0 ? typeName.Substring(0, p) : typeName;
361+
362+
// globalize anything that is ambiguous
363+
if (IsAmbiguousSymbol(symbol))
364+
typeName = "global::" + s.Replace("+", ".");
357365
}
358366

359-
sb.Append(s);
367+
sb.Append(typeName);
360368
}
361369

362370
private static string XmlCommentString(string s)

0 commit comments

Comments
 (0)