// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;

using ICSharpCode.NRefactory.CSharp.TypeSystem;
using ICSharpCode.NRefactory.Semantics;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.TypeSystem.Implementation;
using NUnit.Framework;

namespace ICSharpCode.NRefactory.CSharp.Resolver
{
	[TestFixture]
	public class NameLookupTests : ResolverTestBase
	{
		CSharpResolver resolver;
		
		public override void SetUp()
		{
			base.SetUp();
			resolver = new CSharpResolver(compilation).WithCurrentUsingScope(MakeUsingScope(string.Empty));
		}
		
		ResolvedUsingScope MakeUsingScope(string namespaceName = "", string[] usings = null, KeyValuePair<string, string>[] usingAliases = null)
		{
			UsingScope usingScope = new UsingScope();
			if (!string.IsNullOrEmpty(namespaceName)) {
				foreach (string element in namespaceName.Split('.')) {
					usingScope = new UsingScope(usingScope, string.IsNullOrEmpty(usingScope.NamespaceName) ? element : usingScope.NamespaceName + "." + element);
				}
			}
			if (usings != null) {
				foreach (string u in usings)
					usingScope.Usings.Add(MakeReference(u));
			}
			if (usingAliases != null) {
				foreach (var pair in usingAliases)
					usingScope.UsingAliases.Add(new KeyValuePair<string, TypeOrNamespaceReference>(pair.Key, MakeReference(pair.Value)));
			}
			return usingScope.Resolve(compilation);
		}
		
		[Test]
		public void SimpleNameLookupWithoutContext()
		{
			// without any using scope, we still want to find elements of the global namespace:
			var nrr = (NamespaceResolveResult)resolver.WithCurrentUsingScope(null).ResolveSimpleName("System", new IType[0]);
			Assert.AreEqual("System", nrr.NamespaceName);
		}
		
		[Test]
		public void SimpleNamespaceLookup()
		{
			NamespaceResolveResult nrr = (NamespaceResolveResult)resolver.ResolveSimpleName("System", new IType[0]);
			Assert.AreEqual("System", nrr.NamespaceName);
			Assert.AreSame(SpecialType.UnknownType, nrr.Type);
		}
		
		[Test]
		public void NamespaceInParentNamespaceLookup()
		{
			var resolverWithUsing = resolver.WithCurrentUsingScope(MakeUsingScope("System.Collections.Generic"));
			NamespaceResolveResult nrr = (NamespaceResolveResult)resolverWithUsing.ResolveSimpleName("Text", new IType[0]);
			Assert.AreEqual("System.Text", nrr.NamespaceName);
		}
		
		[Test]
		public void NamespacesAreNotImported()
		{
			var resolverWithUsing = resolver.WithCurrentUsingScope(MakeUsingScope(usings: new [] { "System" }));
			Assert.IsTrue(resolverWithUsing.ResolveSimpleName("Collections", new IType[0]).IsError);
		}
		
		[Test]
		public void ImportedType()
		{
			var resolverWithUsing = resolver.WithCurrentUsingScope(MakeUsingScope(usings: new [] { "System" }));
			TypeResolveResult trr = (TypeResolveResult)resolverWithUsing.ResolveSimpleName("String", new IType[0]);
			Assert.AreEqual("System.String", trr.Type.FullName);
		}
		
		[Test]
		public void UnknownIdentifierTest()
		{
			UnknownIdentifierResolveResult uirr = (UnknownIdentifierResolveResult)resolver.ResolveSimpleName("xyz", new IType[0]);
			Assert.IsTrue(uirr.IsError);
			Assert.AreEqual("xyz", uirr.Identifier);
		}
		
		[Test]
		public void GlobalIsUnknownIdentifier()
		{
			Assert.IsTrue(resolver.ResolveSimpleName("global", new IType[0]).IsError);
		}
		
		[Test]
		public void GlobalIsAlias()
		{
			NamespaceResolveResult nrr = (NamespaceResolveResult)resolver.ResolveAlias("global");
			Assert.AreEqual("", nrr.NamespaceName);
		}
		
		[Test]
		public void AliasToImportedType()
		{
			var resolverWithUsing = resolver.WithCurrentUsingScope(MakeUsingScope(usings: new [] { "System" }, usingAliases: new [] { new KeyValuePair<string, string>( "x", "String" )}));
			UnknownIdentifierResolveResult rr = (UnknownIdentifierResolveResult)resolverWithUsing.ResolveSimpleName("x", new IType[0]);
			// Unknown identifier (as String isn't looked up in System)
			Assert.AreEqual("String", rr.Identifier);
		}
		
		[Test]
		public void AliasToImportedType2()
		{
			UsingScope mainUsingScope = new UsingScope();
			mainUsingScope.Usings.Add(MakeReference("System"));
			UsingScope nestedUsingScope = new UsingScope(mainUsingScope, "SomeNamespace");
			nestedUsingScope.UsingAliases.Add(new KeyValuePair<string, TypeOrNamespaceReference>("x", MakeReference("String")));
			var resolverWithUsing = resolver.WithCurrentUsingScope(nestedUsingScope.Resolve(compilation));
			
			TypeResolveResult trr = (TypeResolveResult)resolverWithUsing.ResolveSimpleName("x", new IType[0]);
			Assert.AreEqual("System.String", trr.Type.FullName);
		}
		
		[Test]
		public void AliasOperatorOnTypeAlias()
		{
			var resolverWithUsing = resolver.WithCurrentUsingScope(MakeUsingScope(usingAliases: new [] { new KeyValuePair<string, string>( "x", "System.String" )}));
			Assert.IsTrue(resolverWithUsing.ResolveAlias("x").IsError);
		}
		
		[Test]
		public void AliasOperatorOnNamespaceAlias()
		{
			var resolverWithUsing = resolver.WithCurrentUsingScope(MakeUsingScope(usingAliases: new [] { new KeyValuePair<string, string>( "x", "System.Collections.Generic" )}));
			NamespaceResolveResult nrr = (NamespaceResolveResult)resolverWithUsing.ResolveAlias("x");
			Assert.AreEqual("System.Collections.Generic", nrr.NamespaceName);
		}
		
		[Test]
		public void AliasOperatorOnNamespace()
		{
			Assert.IsTrue(resolver.ResolveAlias("System").IsError);
		}
		
		[Test]
		public void FindClassInCurrentNamespace()
		{
			var resolverWithUsing = resolver.WithCurrentUsingScope(MakeUsingScope("System.Collections"));
			TypeResolveResult trr = (TypeResolveResult)resolverWithUsing.ResolveSimpleName("String", new IType[0]);
			Assert.AreEqual("System.String", trr.Type.FullName);
		}
		
		[Test]
		public void FindNeighborNamespace()
		{
			var resolverWithUsing = resolver.WithCurrentUsingScope(MakeUsingScope("System.Collections"));
			NamespaceResolveResult nrr = (NamespaceResolveResult)resolverWithUsing.ResolveSimpleName("Text", new IType[0]);
			Assert.AreEqual("System.Text", nrr.NamespaceName);
		}
		
		[Test]
		public void FindTypeParameters()
		{
			var listOfT = compilation.FindType(typeof(List<>)).GetDefinition();
			var resolverWithContext = resolver
				.WithCurrentUsingScope(MakeUsingScope("System.Collections.Generic"))
				.WithCurrentTypeDefinition(listOfT)
				.WithCurrentMember(listOfT.Methods.Single(m => m.Name == "ConvertAll"));
			
			TypeResolveResult trr;
			trr = (TypeResolveResult)resolverWithContext.ResolveSimpleName("TOutput", new IType[0]);
			Assert.AreSame(((IMethod)resolverWithContext.CurrentMember).TypeParameters[0], trr.Type);
			
			trr = (TypeResolveResult)resolverWithContext.ResolveSimpleName("T", new IType[0]);
			Assert.AreSame(resolverWithContext.CurrentTypeDefinition.TypeParameters[0], trr.Type);
		}
		
		[Test]
		public void SimpleParameter()
		{
			string program = @"class A {
	void Method(string a) {
		string b = $a$;
	}
}
";
			LocalResolveResult result = Resolve<LocalResolveResult>(program);
			Assert.AreEqual("a", result.Variable.Name);
			Assert.IsTrue(result.IsParameter);
			Assert.AreEqual("System.String", result.Type.FullName);
		}
		
		[Test]
		public void SimpleLocalVariable()
		{
			string program = @"class A {
	void Method() {
		string a;
		string b = $a$;
	}
}
";
			LocalResolveResult result = Resolve<LocalResolveResult>(program);
			Assert.AreEqual("a", result.Variable.Name);
			Assert.IsFalse(result.IsParameter);
			
			Assert.AreEqual("System.String", result.Type.FullName);
		}
		
		[Test]
		public void UnknownTypeTest()
		{
			string program = @"class A {
	void Method($StringBuilder$ b) {
	}
}
";
			UnknownIdentifierResolveResult result = Resolve<UnknownIdentifierResolveResult>(program);
			Assert.AreEqual("StringBuilder", result.Identifier);
			
			Assert.AreSame(SpecialType.UnknownType, result.Type);
		}
		
		const string propertyNameAmbiguousWithTypeNameProgram = @"class A {
	public Color Color { get; set; }
	
	void Method() {
		$
	}
}
class Color {
	public static readonly Color Empty = null;
	public static int M() { }
	public int M(int a) { }
}
";
		
		[Test]
		public void PropertyNameAmbiguousWithTypeName()
		{
			string program = propertyNameAmbiguousWithTypeNameProgram;
			TypeResolveResult trr = Resolve<TypeResolveResult>(program.Replace("$", "$Color$ c;"));
			Assert.AreEqual("Color", trr.Type.Name);
			
			MemberResolveResult mrr = Resolve<MemberResolveResult>(program.Replace("$", "x = $Color$;"));
			Assert.AreEqual("Color", mrr.Member.Name);
		}
		
		[Test]
		public void PropertyNameAmbiguousWithTypeName_MemberAccessOnAmbiguousIdentifier()
		{
			string program = propertyNameAmbiguousWithTypeNameProgram;
			Resolve<MemberResolveResult>(program.Replace("$", "$Color$ = Color.Empty;"));
			Resolve<TypeResolveResult>(program.Replace("$", "Color = $Color$.Empty;"));
		}
		
		[Test]
		public void PropertyNameAmbiguousWithTypeName_MethodInvocationOnAmbiguousIdentifier()
		{
			string program = propertyNameAmbiguousWithTypeNameProgram;
			Resolve<MemberResolveResult>(program.Replace("$", "x = $Color$.M(1);"));
			Resolve<TypeResolveResult>(program.Replace("$", "x = $Color$.M();"));
		}
		
		[Test]
		public void ValueInsideSetterTest()
		{
			string program = @"class A {
	public string Property {
		set {
			var a = $value$;
		}
	}
}
";
			LocalResolveResult result = Resolve<LocalResolveResult>(program);
			Assert.AreEqual("System.String", result.Type.FullName);
			Assert.AreEqual("value", result.Variable.Name);
		}
		
		[Test]
		public void ValueInsideEventTest()
		{
			string program = @"using System; class A {
	public event EventHandler Ev {
		add {
			var a = $value$;
		}
		remove {}
	}
}
";
			LocalResolveResult result = Resolve<LocalResolveResult>(program);
			Assert.AreEqual("System.EventHandler", result.Type.FullName);
			Assert.AreEqual("value", result.Variable.Name);
		}
		
		[Test]
		public void ValueInsideIndexerSetterTest()
		{
			string program = @"using System; class A {
	public string this[int arg] {
		set {
			var a = $value$;
		}
	}
}
";
			LocalResolveResult result = Resolve<LocalResolveResult>(program);
			Assert.AreEqual("System.String", result.Type.FullName);
			Assert.AreEqual("value", result.Variable.Name);
		}
		
		[Test]
		public void AnonymousMethodParameters()
		{
			string program = @"using System;
class A {
	void Method() {
		SomeEvent += delegate(object sender, EventArgs e) {
			$e$.ToString();
		};
	} }
";
			LocalResolveResult result = Resolve<LocalResolveResult>(program);
			Assert.AreEqual("System.EventArgs", result.Type.FullName);
			Assert.AreEqual("e", result.Variable.Name);
		}
		
		[Test]
		public void DefaultTypeCSharp()
		{
			string program = @"class A {
	void Method() {
		$int$ a;
	} }
";
			TypeResolveResult result = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("System.Int32", result.Type.FullName);
		}
		
		[Test]
		public void LoopVariableScopeTest()
		{
			string program = @"using System;
class TestClass {
	void Test() {
		for (int i = 0; i < 10; i++) {
			$1$.ToString();
		}
		for (long i = 0; i < 10; i++) {
			$2$.ToString();
		}
	}
}
";
			LocalResolveResult lr = Resolve<LocalResolveResult>(program.Replace("$1$", "$i$").Replace("$2$", "i"));
			Assert.AreEqual("System.Int32", lr.Type.ReflectionName);
			
			lr = Resolve<LocalResolveResult>(program.Replace("$1$", "i").Replace("$2$", "$i$"));
			Assert.AreEqual("System.Int64", lr.Type.ReflectionName);
		}
		
		[Test]
		public void NamespacePreferenceTest()
		{
			// Classes in the current namespace are preferred over classes from
			// imported namespaces
			string program = @"using System;
namespace Testnamespace {
class A {
	$Activator$ a;
}

class Activator {
	
}
}
";
			var result = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("Testnamespace.Activator", result.Type.FullName);
		}
		
		[Test]
		public void ParentNamespaceTypeLookup()
		{
			string program = @"using System;
namespace Root {
  class Alpha {}
}
namespace Root.Child {
  class Beta {
    $Alpha$ a;
  }
}
";
			var result = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("Root.Alpha", result.Type.FullName);
		}
		
		[Test]
		public void ImportAliasTest()
		{
			string program = @"using COL = System.Collections;
class TestClass {
	COL.ArrayList ff;
}
";
			TypeResolveResult type = Resolve<TypeResolveResult>(program.Replace("COL.ArrayList", "$COL.ArrayList$"));
			Assert.IsNotNull(type, "COL.ArrayList should resolve to a type");
			Assert.AreEqual("System.Collections.ArrayList", type.Type.FullName, "TypeResolveResult");
			
			MemberResolveResult member = Resolve<MemberResolveResult>(program.Replace("ff", "$ff$"));
			Assert.AreEqual("System.Collections.ArrayList", member.Type.FullName, "the full type should be resolved");
		}
		
		[Test]
		public void ImportAliasNamespaceResolveTest()
		{
			NamespaceResolveResult ns;
			string program = "using COL = System.Collections;\r\nclass A {\r\n$.ArrayList a;\r\n}\r\n";
			ns = Resolve<AliasNamespaceResolveResult>(program.Replace("$", "$COL$"));
			Assert.AreEqual("System.Collections", ns.NamespaceName, "COL");
			ns = Resolve<NamespaceResolveResult>(program.Replace("$", "$COL.Generic$"));
			Assert.AreEqual("System.Collections.Generic", ns.NamespaceName, "COL.Generic");
		}
		
		[Test]
		public void ImportAliasClassResolveTest()
		{
			string program = @"using COL = System.Collections.ArrayList;
class TestClass {
	void Test() {
		COL a = new COL();
		
	}
}
";
			TypeResolveResult trr = Resolve<AliasTypeResolveResult>(program.Replace("COL a", "$COL$ a"));
			Assert.AreEqual("System.Collections.ArrayList", trr.Type.FullName, "COL");
			ResolveResult rr = Resolve<CSharpInvocationResolveResult>(program.Replace("new COL()", "$new COL()$"));
			Assert.AreEqual("System.Collections.ArrayList", rr.Type.FullName, "a");
		}
		
		[Test]
		public void ImportSubnamespaceWithAliasTest()
		{
			string program = @"namespace PC
{
	// Define an alias for the nested namespace.
	using Project = PC.MyCompany.Project;
	class A
	{
		Project.MyClass M()
		{
			// Use the alias
			$Project.MyClass$ mc = new Project.MyClass();
			return mc;
		}
	}
	namespace MyCompany
	{
		namespace Project
		{
			public class MyClass { }
		}
	}
}
";
			var mrr = Resolve(program);
			Assert.AreEqual("PC.MyCompany.Project.MyClass", mrr.Type.FullName);
		}
		
		[Test]
		public void ResolveNamespaceSD_863()
		{
			string program = @"using System;
namespace A.C { class D {} }
namespace A.B.C { class D {} }
namespace A.B {
	class TestClass {
		void Test() {
			C.D x;
		}
	}
}
";
			NamespaceResolveResult nrr = Resolve<NamespaceResolveResult>(program.Replace("C.D", "$C$.D"));
			Assert.AreEqual("A.B.C", nrr.NamespaceName, "nrr.Name");
			TypeResolveResult trr = Resolve<TypeResolveResult>(program.Replace("C.D", "$C.D$"));
			Assert.AreEqual("A.B.C.D", trr.Type.FullName);
		}
		
		[Test]
		public void ResolveTypeSD_863()
		{
			string program = @"using System;
namespace A { class C {} }
namespace A.B {
	class C {}
	class TestClass {
		void Test() {
			$C$ a;
		}
	}
}
";
			TypeResolveResult trr = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("A.B.C", trr.Type.FullName);
		}
		
		
		[Test]
		public void InnerTypeResolve1 ()
		{
			string program = @"public class C<T> { public class Inner { } }
class TestClass {
	void Test() {
		$C<string>.Inner$ a;
	}
}
";
			TypeResolveResult trr = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("C.Inner", trr.Type.FullName);
		}
		
		[Test]
		public void InnerTypeResolve2 ()
		{
			string program = @"public class C<T> { public class D<S,U> { public class Inner { } }}
class TestClass {
	void Test() {
		$C<string>.D<int,TestClass>.Inner$ a;
	}
}
";
			TypeResolveResult trr = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("C.D.Inner", trr.Type.FullName);
		}
		
		[Test]
		public void ShortMaxValueTest()
		{
			string program = @"using System;
class TestClass {
	object a = $short.MaxValue$;
}
";
			MemberResolveResult rr = Resolve<MemberResolveResult>(program);
			Assert.AreEqual("System.Int16", rr.Type.FullName);
			Assert.AreEqual("System.Int16.MaxValue", rr.Member.FullName);
			Assert.AreEqual(short.MaxValue, rr.ConstantValue);
		}
		
		[Test]
		public void ClassWithSameNameAsNamespace()
		{
			string program = @"using System; namespace XX {
	class Test {
		static void X() {
			a = $;
		}
	}
	class XX {
		public static void Test() {}
	} }";
			TypeResolveResult trr = Resolve<TypeResolveResult>(program.Replace("$", "$XX$"));
			Assert.AreEqual("XX.XX", trr.Type.FullName);
			
			NamespaceResolveResult nrr = Resolve<NamespaceResolveResult>(program.Replace("$", "$global::XX$.T"));
			Assert.AreEqual("XX", nrr.NamespaceName);
			
			trr = Resolve<TypeResolveResult>(program.Replace("$", "$global::XX.XX$"));
			Assert.AreEqual("XX.XX", trr.Type.FullName);
			
			InvocationResolveResult mrr = Resolve<CSharpInvocationResolveResult>(program.Replace("$", "$XX.Test()$"));
			Assert.AreEqual("XX.XX.Test", mrr.Member.FullName);
		}
		
		[Test]
		public void ClassNameLookup1()
		{
			string program = @"namespace MainNamespace {
	using Test.Subnamespace;
	class Program {
		static void M($Test.TheClass$ c) {}
	}
}

namespace Test { public class TheClass { } }
namespace Test.Subnamespace {
	public class Test { public class TheClass { } }
}
";
			TypeResolveResult trr = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("Test.Subnamespace.Test.TheClass", trr.Type.FullName);
		}
		
		[Test]
		public void ClassNameLookup2()
		{
			string program = @"using Test.Subnamespace;
namespace MainNamespace {
	class Program {
		static void M($Test.TheClass$ c) {}
	}
}

namespace Test { public class TheClass { } }
namespace Test.Subnamespace {
	public class Test { public class TheClass { } }
}
";
			TypeResolveResult trr = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("Test.TheClass", trr.Type.FullName);
		}
		
		[Test]
		public void ClassNameLookup3()
		{
			string program = @"namespace MainNamespace {
	using Test.Subnamespace;
	class Program {
		static void M($Test$ c) {}
	}
}

namespace Test { public class TheClass { } }
namespace Test.Subnamespace {
	public class Test { public class TheClass { } }
}
";
			TypeResolveResult trr = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("Test.Subnamespace.Test", trr.Type.FullName);
		}
		
		[Test]
		public void ClassNameLookup4()
		{
			string program = @"using Test.Subnamespace;
namespace MainNamespace {
	class Program {
		static void M($Test$ c) {}
	}
}

namespace Test { public class TheClass { } }
namespace Test.Subnamespace {
	public class Test { public class TheClass { } }
}
";
			NamespaceResolveResult nrr = Resolve<NamespaceResolveResult>(program);
			Assert.AreEqual("Test", nrr.NamespaceName);
		}
		
		[Test]
		public void ClassNameLookup5()
		{
			string program = @"namespace MainNamespace {
	using A;
	
	class M {
		void X($Test$ a) {}
	}
	namespace Test { class B {} }
}

namespace A {
	class Test {}
}";
			NamespaceResolveResult nrr = Resolve<NamespaceResolveResult>(program);
			Assert.AreEqual("MainNamespace.Test", nrr.NamespaceName);
		}
		
		[Test]
		public void InvocableRule()
		{
			string program = @"using System;
	class DerivedClass : BaseClass {
		static void X() {
			a = $;
		}
		private static new int Test;
	}
	class BaseClass {
		public static string Test() {}
	}";
			MemberResolveResult mrr = Resolve<CSharpInvocationResolveResult>(program.Replace("$", "$BaseClass.Test()$"));
			Assert.AreEqual("BaseClass.Test", mrr.Member.FullName);
			
			mrr = Resolve<MemberResolveResult>(program.Replace("$", "$Test$"));
			Assert.AreEqual("DerivedClass.Test", mrr.Member.FullName);
			
			mrr = Resolve<MemberResolveResult>(program.Replace("$", "$DerivedClass.Test$"));
			Assert.AreEqual("DerivedClass.Test", mrr.Member.FullName);
			
			// returns BaseClass.Test because DerivedClass.Test is not invocable
			mrr = Resolve<CSharpInvocationResolveResult>(program.Replace("$", "$DerivedClass.Test()$"));
			Assert.AreEqual("BaseClass.Test", mrr.Member.FullName);
		}
		
		[Test]
		public void InvocableRule2()
		{
			string program = @"using System;
	class DerivedClass : BaseClass {
		static void X() {
			a = $;
		}
		private static new int Test;
	}
	delegate string SomeDelegate();
	class BaseClass {
		public static SomeDelegate Test;
	}";
			MemberResolveResult mrr = Resolve<MemberResolveResult>(program.Replace("$", "$BaseClass.Test$()"));
			Assert.AreEqual("BaseClass.Test", mrr.Member.FullName);
			
			mrr = Resolve<MemberResolveResult>(program.Replace("$", "$Test$"));
			Assert.AreEqual("DerivedClass.Test", mrr.Member.FullName);
			
			mrr = Resolve<MemberResolveResult>(program.Replace("$", "$DerivedClass.Test$"));
			Assert.AreEqual("DerivedClass.Test", mrr.Member.FullName);
			
			// returns BaseClass.Test because DerivedClass.Test is not invocable
			mrr = Resolve<MemberResolveResult>(program.Replace("$", "$DerivedClass.Test$()"));
			Assert.AreEqual("BaseClass.Test", mrr.Member.FullName);
		}
		
		[Test]
		public void AccessibleRule()
		{
			string program = @"using System;
	class BaseClass {
		static void X() {
			a = $DerivedClass.Test$;
		}
		public static int Test;
	}
	class DerivedClass : BaseClass {
		private static new int Test;
	}
	";
			// returns BaseClass.Test because DerivedClass.Test is not accessible
			MemberResolveResult mrr = Resolve<MemberResolveResult>(program);
			Assert.AreEqual("BaseClass.Test", mrr.Member.FullName);
		}
		
		[Test]
		public void FieldHidingProperty()
		{
			string program = @"using System;
	class DerivedClass : BaseClass {
		static void X() {
			a = $Test$;
		}
		public static new int Test;
	}
	class BaseClass {
		public static int Test { get { return 0; } }
	}
	";
			MemberResolveResult mrr = Resolve<MemberResolveResult>(program);
			Assert.AreEqual("DerivedClass.Test", mrr.Member.FullName);
		}
		
		[Test]
		public void PropertyHidingField()
		{
			string program = @"using System;
	class DerivedClass : BaseClass {
		static void X() {
			a = $Test$;
		}
		public static new int Test { get { return 0; } }
	}
	class BaseClass {
		public static int Test;
	}
	";
			MemberResolveResult mrr = Resolve<MemberResolveResult>(program);
			Assert.AreEqual("DerivedClass.Test", mrr.Member.FullName);
		}
		
		[Test]
		public void SD_1487()
		{
			string program = @"using System;
class C2 : C1 {
	public static void M() {
		a = $;
	}
}
class C1 {
	protected static int Field;
}";
			MemberResolveResult mrr;
			mrr = Resolve<MemberResolveResult>(program.Replace("$", "$Field$"));
			Assert.IsFalse(mrr.IsError);
			Assert.AreEqual("C1.Field", mrr.Member.FullName);
			
			mrr = Resolve<MemberResolveResult>(program.Replace("$", "$C1.Field$"));
			Assert.IsFalse(mrr.IsError);
			Assert.AreEqual("C1.Field", mrr.Member.FullName);
			
			mrr = Resolve<MemberResolveResult>(program.Replace("$", "$C2.Field$"));
			Assert.IsFalse(mrr.IsError);
			Assert.AreEqual("C1.Field", mrr.Member.FullName);
		}
		
		[Test]
		public void NullableValue()
		{
			string program = @"using System;
class Test {
	public static void M(int? a) {
		$a.Value$.ToString();
	}
}";
			MemberResolveResult rr = Resolve<MemberResolveResult>(program);
			Assert.AreEqual("System.Nullable.Value", rr.Member.FullName);
			Assert.AreEqual("System.Int32", rr.Member.ReturnType.FullName);
		}
		
		[Test]
		public void MethodHidesEvent()
		{
			// see SD-1542
			string program = @"using System;
class Test : Form {
	public Test() {
		a = $base.KeyDown$;
	}
	void KeyDown(object sender, EventArgs e) {}
}
class Form {
	public event EventHandler KeyDown;
}";
			var mrr = Resolve<MemberResolveResult>(program);
			Assert.AreEqual("Form.KeyDown", mrr.Member.FullName);
			
			var mgrr = Resolve<MethodGroupResolveResult>(program.Replace("base", "this"));
			Assert.AreEqual("Test.KeyDown", mgrr.Methods.Single().FullName);
		}
		
		[Test]
		public void ProtectedMemberVisibleWhenBaseTypeReferenceIsInOtherPart()
		{
			string program = @"using System;
partial class A {
	void M1() {
		$x$ = 0;
	}
}
partial class A : B { }
class B
{
	protected int x;
}";
			var mrr = Resolve<MemberResolveResult>(program);
			Assert.AreEqual("B.x", mrr.Member.FullName);
		}
		
		[Test]
		public void SubstituteClassAndMethodTypeParametersAtOnce()
		{
			string program = @"class C<X> { static void M<T>(X a, T b) { $C<T>.M<X>$(b, a); } }";
			var rr = Resolve<MethodGroupResolveResult>(program);
			Assert.AreEqual("X", rr.TypeArguments.Single().Name);
			
			var m = rr.Methods.Single();
			Assert.AreSame(rr.TypeArguments.Single(), m.TypeArguments.Single());
			Assert.AreEqual("T", m.Parameters[0].Type.Name);
			Assert.AreEqual("X", m.Parameters[1].Type.Name);
		}
		
		[Test]
		public void InheritingInnerClassShouldNotCauseStackOverflow()
		{
			string program = @"class Test : $Test.Base$, Test.ITest { public class Base {} interface ITest {} }";
			var result = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("Test.Base", result.Type.FullName);
		}
		
		[Test]
		public void InnerClassInheritingFromProtectedBaseInnerClassShouldNotCauseStackOverflow()
		{
			string program = @"class Base {
  protected class NestedBase {}
}
class Derived : Base {
  class NestedDerived : $NestedBase$ { }
}";
			var result = Resolve<TypeResolveResult>(program);
			Assert.AreEqual("Base.NestedBase", result.Type.FullName);
		}
		
		[Test]
		public void EnumMembersHaveUnderlyingTypeWithinInitializers_MemberFromSameEnum()
		{
			string program = @"enum E { A = 0, B = $A$ + 1 }";
			var result = Resolve<MemberResolveResult>(program);
			Assert.AreEqual("E.A", result.Member.FullName);
			Assert.AreEqual("System.Int32", result.Type.ReflectionName);
			Assert.AreEqual("E", result.Member.ReturnType.ReflectionName);
		}
		
		[Test]
		public void EnumMembersHaveUnderlyingTypeWithinInitializers_MemberFromOtherEnum()
		{
			string program = @"enum A : ushort { X = 1 } enum B { Y = $A.X$ }";
			var result = Resolve<MemberResolveResult>(program);
			Assert.AreEqual("A.X", result.Member.FullName);
			Assert.AreEqual("System.UInt16", result.Type.ReflectionName);
			Assert.AreEqual("A", result.Member.ReturnType.ReflectionName);
		}
		
		[Test]
		public void EnumMembersHaveUnderlyingTypeWithinInitializers_RuleDoesNotApplyToAttributes()
		{
			string program = @"enum A { X = 1, [SomeAttribute($X$)] Y }";
			var result = Resolve<MemberResolveResult>(program);
			Assert.AreEqual("A.X", result.Member.FullName);
			Assert.AreEqual("A", result.Type.ReflectionName);
		}

		[Test]
		public void InheritFromProtectedInnerClassTest()
		{
			string program = @"
class Test
{
	protected class Foo
	{
		public int Bar = 0;
	}
}

class MainClass : Test
{
	class Foo2 : Test.Foo
	{
		public Foo2 ()
		{
			Console.WriteLine ($Bar$);
		}
	}
}
";
			var result = Resolve<MemberResolveResult>(program);
			Assert.IsFalse(result.IsError);
			Assert.AreEqual("Test.Foo.Bar", result.Member.FullName);
		}

		[Test]
		public void LocalInsideUnsafeBlock()
		{
			string program = @"class A {
	void Method() {
		unsafe {
			string a;
			string b = $a$;
		}
	}
}
";
			LocalResolveResult result = Resolve<LocalResolveResult>(program);
			Assert.AreEqual("a", result.Variable.Name);
			Assert.IsFalse(result.IsParameter);
			
			Assert.AreEqual("System.String", result.Type.FullName);
		}

		[Test]
		public void DuplicateUsingDirective() {
			string program = @"
using foo;
using foo;
namespace bar {
	using foo;
	using foo;

    public class Bar {
        public void M() {
            new $Foo$();
        }
    }
}
namespace foo {
    public class Foo {
    }
}";

			var result = Resolve<TypeResolveResult>(program);
			Assert.IsFalse(result.IsError);
			Assert.AreEqual("foo.Foo", result.Type.FullName);

		}
		
		[Test]
		public void BaseTypeReference_refers_to_outer_type()
		{
			string program = @"class B {}
    class A : $B$ {
        class B {}
    }";
			var result = Resolve<TypeResolveResult>(program);
			Assert.IsFalse(result.IsError);
			Assert.AreEqual("B", result.Type.FullName);
			
			// also check if the reference in the type system is correct
			var a = ((ITypeDefinition)result.Type).Compilation.RootNamespace.GetTypeDefinition("A", 0);
			Assert.AreEqual("B", a.DirectBaseTypes.Single().FullName);
		}
		
		[Test]
		public void Class_constraint_refers_to_outer_type()
		{
			string program = @"class B {}
    class A<T> where T : $B$ {
        class B {}
    }";
			var result = Resolve<TypeResolveResult>(program);
			Assert.IsFalse(result.IsError);
			Assert.AreEqual("B", result.Type.FullName);
			
			// also check if the reference in the type system is correct
			var a = ((ITypeDefinition)result.Type).Compilation.RootNamespace.GetTypeDefinition("A", 1);
			Assert.AreEqual("B", a.TypeParameters.Single().DirectBaseTypes.Single().FullName);
		}
		
		[Test]
		public void Method_constraint_refers_to_inner_type()
		{
			string program = @"class B {}
    class A {
    	void M<T>() where T : $B$ {}
        class B {}
    }";
			var result = Resolve<TypeResolveResult>(program);
			Assert.IsFalse(result.IsError);
			Assert.AreEqual("A.B", result.Type.FullName);
			
			// also check if the reference in the type system is correct
			var a = ((ITypeDefinition)result.Type).Compilation.RootNamespace.GetTypeDefinition("A", 0);
			var method = a.Methods.Single(m => m.Name == "M");
			Assert.AreEqual("A.B", method.TypeParameters.Single().DirectBaseTypes.Single().FullName);
		}

		[Test]
		public void EmptyNamespaces()
		{
			// should maybe a typesystem test - but empty namespaces don't make sense in cecil.
			string program = @"namespace A.B.C.D
{

}

namespace Test
{
    using $A.B.C.D$;

    public class C
    {
        public static void Main ()
        {

        }
    }
}

	";
			var nrr = Resolve<NamespaceResolveResult>(program);
			Assert.AreEqual("A.B.C.D", nrr.NamespaceName);
		}
		
		[Test]
		public void CuriouslyRecurringInnerClass()
		{
			string program = @"
	abstract class Table<T>
	{
		internal T[] records = {};
	}
	
	sealed class ConcreteTable : Table<ConcreteTable.Record>
	{
		internal struct Record
		{
			public int Value;
		}
		
		int First()
		{
			return $records[0].Value$;
		}
	}";
			var rr = Resolve<MemberResolveResult>(program);
			Assert.IsFalse(rr.IsError);
			Assert.AreEqual("ConcreteTable+Record.Value", rr.Member.ReflectionName);
		}
	}
}
