diff --git a/Clojure/Clojure/Clojure.csproj b/Clojure/Clojure/Clojure.csproj
index bf1921a8..93f0b501 100644
--- a/Clojure/Clojure/Clojure.csproj
+++ b/Clojure/Clojure/Clojure.csproj
@@ -25,7 +25,6 @@
-
Never
@@ -67,7 +66,7 @@
-->
-
+
%(Filename)$(Extension)
diff --git a/Clojure/Clojure/Lib/ClrTypeSpec.cs b/Clojure/Clojure/Lib/ClrTypeSpec.cs
index 94f4c12f..fd91daba 100644
--- a/Clojure/Clojure/Lib/ClrTypeSpec.cs
+++ b/Clojure/Clojure/Lib/ClrTypeSpec.cs
@@ -1,365 +1,811 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Reflection;
+using System.Text;
+
+namespace clojure.lang.TypeName;
+
+// Inspired by similar code in various places
+// See https://github.com/mono/mono/blob/master/mcs/class/corlib/System/TypeSpec.cs
+// and see http://www.java2s.com/Open-Source/ASP.NET/Library/sixpack-library/SixPack/Reflection/TypeName.cs.htm
+// The EBNF for fully-qualified type names is here: http://msdn.microsoft.com/en-us/library/yfsftwz6(v=VS.100).aspx
+// I primarily followed the mono version. Modifications have to do with assembly and type resolver defaults and some minor details.
+// Also, rather than throwing exceptions for badly formed names, we just return null. Where this is called, generally, an error is not required.
+//
+// Giving credit where credit is due: please note the following attributions in the mono code:
+//
+// Author:
+// Rodrigo Kumpera
+//
+// Copyright (C) 2010 Novell, Inc (http://www.novell.com)
+//
+// Since my initial pull in 2012, Mono made at least seven commits updating this code.
+// 2025.09.11 -- bring this code up to date.
+
+
+// A TypeName is wrapper around type names in display form
+// (that is, with special characters escaped).
+//
+// Note that in general if you unescape a type name, you will
+// lose information: If the type name's DisplayName is
+// Foo\+Bar+Baz (outer class ``Foo+Bar``, inner class Baz)
+// unescaping the first plus will give you (outer class Foo,
+// inner class Bar, innermost class Baz).
+//
+// The correct way to take a TypeName apart is to feed its
+// DisplayName to TypeSpec.Parse()
+//
+public interface IClrTypeName : System.IEquatable
+{
+ string DisplayName { get; }
+
+ // add a nested name under this one.
+ IClrTypeName NestedName(IClrTypeIdentifier innerName);
+}
-namespace clojure.lang
+// A type identifier is a single component of a type name.
+// Unlike a general typename, a type identifier can be be
+// converted to internal form without loss of information.
+public interface IClrTypeIdentifier : IClrTypeName
{
- // Inspired by similar code in various places
- // See https://github.com/mono/mono/blob/master/mcs/class/corlib/System/TypeSpec.cs
- // and see http://www.java2s.com/Open-Source/ASP.NET/Library/sixpack-library/SixPack/Reflection/TypeName.cs.htm
- // The EBNF for fully-qualified type names is here: http://msdn.microsoft.com/en-us/library/yfsftwz6(v=VS.100).aspx
- // I primarily followed the mono version. Modifications have to do with assembly and type resolver defaults and some minor details.
- // Also, rather than throwing exceptions for badly formed names, we just return null. Where this is called, generally, an error is not required.
- //
- // Giving credit where credit is due: please note the following attributions in the mono code:
- //
- // Author:
- // Rodrigo Kumpera
- //
- // Copyright (C) 2010 Novell, Inc (http://www.novell.com)
+ string InternalName { get; }
+}
- public class ClrArraySpec
+internal class ClrTypeNames
+{
+ internal static IClrTypeName FromDisplay(string displayName)
{
+ return new Display(displayName);
+ }
- #region Data
+ internal abstract class ATypeName : IClrTypeName
+ {
+ public abstract string DisplayName { get; }
- private readonly int _dimensions;
- private readonly bool _isBound;
+ public abstract IClrTypeName NestedName(IClrTypeIdentifier innerName);
- #endregion
+ public bool Equals(IClrTypeName other)
+ {
+ return other != null && DisplayName == other.DisplayName;
+ }
- #region C-tors
+ public override int GetHashCode()
+ {
+ return DisplayName.GetHashCode();
+ }
- internal ClrArraySpec(int dimensions, bool bound)
+ public override bool Equals(object other)
{
- this._dimensions = dimensions;
- this._isBound = bound;
+ return Equals(other as IClrTypeName);
}
+ }
- #endregion
- #region Resolving
+ internal class Display : ATypeName
+ {
+ string _displayName;
- internal Type Resolve(Type type)
+ internal Display(string displayName)
{
- if (_isBound)
- return type.MakeArrayType(1);
- else if (_dimensions == 1)
- return type.MakeArrayType();
- return type.MakeArrayType(_dimensions);
+ this._displayName = displayName;
+ }
+
+ public override string DisplayName { get { return _displayName; } }
+
+ public override IClrTypeName NestedName(IClrTypeIdentifier innerName)
+ {
+ return new Display(DisplayName + "+" + innerName.DisplayName);
}
- #endregion
}
+}
- public class ClrTypeSpec
+internal class ClrTypeIdentifiers
+{
+
+ internal static IClrTypeIdentifier FromDisplay(string displayName)
{
- #region Data
+ return new Display(displayName);
+ }
- string _name;
- string _assemblyName;
- List _nested;
- List _genericParams;
- List _arraySpec;
- int _pointerLevel;
- bool _isByRef;
+ private class Display : ClrTypeNames.ATypeName, IClrTypeIdentifier
+ {
+ string displayName;
+ string internal_name; //cached
- #endregion
+ internal Display(string displayName)
+ {
+ this.displayName = displayName;
+ internal_name = null;
+ }
- #region Entry point
+ public override string DisplayName
+ {
+ get { return displayName; }
+ }
- public static Type GetTypeFromName(string name)
+ public string InternalName
{
- ClrTypeSpec spec = Parse(name);
- if (spec == null)
- return null;
- return spec.Resolve(
- assyName => Assembly.Load(assyName),
- //(assy, typeName) => assy == null ? RT.classForName(typeName) : assy.GetType(typeName)); <--- this goes into an infinite loop on a non-existent typename
- (assy, typeName) => assy == null ? (name.Equals(typeName) ? null : RT.classForName(typeName)) : assy.GetType(typeName));
+ get
+ {
+ if (internal_name == null)
+ internal_name = GetInternalName();
+ return internal_name;
+ }
+ }
+
+ private string GetInternalName()
+ {
+ return ClrTypeSpec.UnescapeInternalName(displayName);
+ }
+
+ public override IClrTypeName NestedName(IClrTypeIdentifier innerName)
+ {
+ return ClrTypeNames.FromDisplay(DisplayName + "+" + innerName.DisplayName);
+ }
+ }
+}
+
+public interface IClrModifierSpec
+{
+ Type Resolve(Type type);
+ StringBuilder Append(StringBuilder sb);
+}
+
+public class ClrArraySpec : IClrModifierSpec
+{
+
+ // dimensions == 1 and bound, or dimensions > 1 and !bound
+ private readonly int _dimensions;
+ private readonly bool _isBound;
+
+ public ClrArraySpec(int dimensions, bool bound)
+ {
+ this._dimensions = dimensions;
+ this._isBound = bound;
+ }
+
+ public override bool Equals(object obj)
+ {
+ var o = obj as ClrArraySpec;
+ if (o == null)
+ return false;
+ return o._dimensions == _dimensions && o._isBound == _isBound;
+ }
+
+ public override int GetHashCode()
+ {
+ return 37 * _dimensions.GetHashCode() + IsBound.GetHashCode();
+ }
+
+ public Type Resolve(Type type)
+ {
+ if (_isBound)
+ return type.MakeArrayType(1);
+ else if (_dimensions == 1)
+ return type.MakeArrayType();
+ return type.MakeArrayType(_dimensions);
+ }
+
+ public StringBuilder Append(StringBuilder sb)
+ {
+ if (_isBound)
+ return sb.Append("[*]");
+ return sb.Append('[')
+ .Append(',', _dimensions - 1)
+ .Append(']');
+ }
+
+ public override string ToString() => Append(new StringBuilder()).ToString();
+
+ public int Rank => _dimensions;
+
+ public bool IsBound => _isBound;
+
+
+}
+
+public class ClrPointerSpec : IClrModifierSpec
+{
+ int pointer_level;
+
+ public ClrPointerSpec(int pointer_level)
+ {
+ this.pointer_level = pointer_level;
+ }
+
+ public override bool Equals(object obj)
+ {
+ var o = obj as ClrPointerSpec;
+ if (o == null)
+ return false;
+ return o.pointer_level == pointer_level;
+ }
+
+ override public int GetHashCode() => pointer_level.GetHashCode();
+
+ public Type Resolve(Type type)
+ {
+ for (int i = 0; i < pointer_level; ++i)
+ type = type.MakePointerType();
+ return type;
+ }
+
+ public StringBuilder Append(StringBuilder sb) => sb.Append('*', pointer_level);
+
+ public override string ToString() => Append(new StringBuilder()).ToString();
+}
+
+public class ClrTypeSpec
+{
+ #region Data
+
+ IClrTypeIdentifier _name;
+ string _assemblyName;
+ List _nested;
+ List _genericParams;
+ List _modifierSpec;
+ bool _isByRef;
+
+ string _displayFullname; // cache
+
+ #endregion
+
+ #region Accessors
+
+ public bool HasModifiers => _modifierSpec is not null;
+ public bool IsNested => _nested is not null && _nested.Count > 0;
+ public bool IsByRef => _isByRef;
+ public IClrTypeName Name => _name;
+ public string AssemblyName => _assemblyName;
+
+ public IEnumerable Nested
+ {
+ get
+ {
+ if (_nested != null)
+ return _nested;
+ else
+ return Array.Empty();
}
+ }
+
+ public IEnumerable Modifiers
+ {
+ get
+ {
+ if (_modifierSpec != null)
+ return _modifierSpec;
+ else
+ return Array.Empty();
+ }
+ }
+
+ public IEnumerable GenericParams
+ {
+ get
+ {
+ if (_genericParams != null)
+ return _genericParams;
+ else
+ return Array.Empty();
+ }
+ }
- #endregion
+ #endregion
- #region Parsing
+ #region Display name-related
- static ClrTypeSpec Parse(string name)
+ [Flags]
+ internal enum DisplayNameFormat
+ {
+ Default = 0x0,
+ WANT_ASSEMBLY = 0x1,
+ NO_MODIFIERS = 0x2,
+ }
+
+ //#if DEBUG
+ public override string ToString()
+ {
+ return GetDisplayFullName(DisplayNameFormat.WANT_ASSEMBLY);
+ }
+ //#endif
+
+ string GetDisplayFullName(DisplayNameFormat flags)
+ {
+ bool wantAssembly = (flags & DisplayNameFormat.WANT_ASSEMBLY) != 0;
+ bool wantModifiers = (flags & DisplayNameFormat.NO_MODIFIERS) == 0;
+
+ var sb = new StringBuilder(_name.DisplayName);
+
+ if (_nested is not null)
{
- int pos = 0;
- ClrTypeSpec spec = Parse(name, ref pos, false, false);
- if (spec == null)
- return null; // bad parse
- if (pos < name.Length)
- return null; // ArgumentException ("Count not parse the whole type name", "typeName");
- return spec;
+ foreach (var n in _nested)
+ sb.Append('+').Append(n.DisplayName);
+ }
+
+ if (_genericParams is not null)
+ {
+ sb.Append('[');
+ for (int i = 0; i < _genericParams.Count; ++i)
+ {
+ if (i > 0)
+ sb.Append(", ");
+ if (_genericParams[i]._assemblyName != null)
+ sb.Append('[').Append(_genericParams[i].DisplayFullName).Append(']');
+ else
+ sb.Append(_genericParams[i].DisplayFullName);
+ }
+ sb.Append(']');
}
- static ClrTypeSpec Parse(string name, ref int p, bool isRecursive, bool allowAssyQualName)
+ if (wantModifiers)
+ GetModifierString(sb);
+
+ if (_assemblyName != null && wantAssembly)
+ sb.Append(", ").Append(_assemblyName);
+
+ return sb.ToString();
+ }
+
+ internal string ModifierString() => GetModifierString(new StringBuilder()).ToString();
+
+ private StringBuilder GetModifierString(StringBuilder sb)
+ {
+ if (_modifierSpec is not null)
{
- int pos = p;
- int name_start;
- bool hasModifiers = false;
- ClrTypeSpec spec = new();
+ foreach (var md in _modifierSpec)
+ md.Append(sb);
+ }
- SkipSpace(name, ref pos);
+ if (_isByRef)
+ sb.Append('&');
- name_start = pos;
+ return sb;
+ }
+
+ internal string DisplayFullName
+ {
+ get
+ {
+ if (_displayFullname is null)
+ _displayFullname = GetDisplayFullName(DisplayNameFormat.Default);
+ return _displayFullname;
+ }
+ }
+
+ internal static string EscapeDisplayName(string internalName)
+ {
+ // initial capacity = length of internalName.
+ // Maybe we won't have to escape anything.
+ var res = new StringBuilder(internalName.Length);
+ foreach (char c in internalName)
+ {
+ switch (c)
+ {
+ case '+':
+ case ',':
+ case '[':
+ case ']':
+ case '*':
+ case '&':
+ case '\\':
+ res.Append('\\').Append(c);
+ break;
+ default:
+ res.Append(c);
+ break;
+ }
+ }
+ return res.ToString();
+ }
+ internal static string UnescapeInternalName(string displayName)
+ {
+ var res = new StringBuilder(displayName.Length);
+ for (int i = 0; i < displayName.Length; ++i)
+ {
+ char c = displayName[i];
+ if (c == '\\')
+ if (++i < displayName.Length)
+ c = displayName[i];
+ res.Append(c);
+ }
+ return res.ToString();
+ }
+
+ internal static bool NeedsEscaping(string internalName)
+ {
+ foreach (char c in internalName)
+ {
+ switch (c)
+ {
+ case ',':
+ case '+':
+ case '*':
+ case '&':
+ case '[':
+ case ']':
+ case '\\':
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+ }
+
+ #endregion
+
+ #region Parsing support
+
+ void AddName(string type_name)
+ {
+ if (_name is null)
+ {
+ _name = ParsedTypeIdentifier(type_name);
+ }
+ else
+ {
+ if (_nested == null)
+ _nested = new List();
+ _nested.Add(ParsedTypeIdentifier(type_name));
+ }
+ }
+
+ void AddModifier(IClrModifierSpec md)
+ {
+ if (_modifierSpec is null)
+ _modifierSpec = new List();
+ _modifierSpec.Add(md);
+ }
+
+ static void SkipSpace(string name, ref int pos)
+ {
+ int p = pos;
+ while (p < name.Length && Char.IsWhiteSpace(name[p]))
+ ++p;
+ pos = p;
+ }
+
+ static void BoundCheck(int idx, string s)
+ {
+ if (idx >= s.Length)
+ throw new ArgumentException("Invalid generic arguments spec", "typeName");
+ }
+
+ static IClrTypeIdentifier ParsedTypeIdentifier(string displayName)
+ {
+ return ClrTypeIdentifiers.FromDisplay(displayName);
+ }
+
+ #endregion
+
+ #region Parsing
+
+ public static ClrTypeSpec Parse(string typeName)
+ {
+ int pos = 0;
+ if (typeName == null)
+ throw new ArgumentNullException("typeName");
+
+ ClrTypeSpec res = Parse(typeName, ref pos, false, true);
+ if (pos < typeName.Length)
+ throw new ArgumentException("Count not parse the whole type name", "typeName");
+ return res;
+ }
+
+ static ClrTypeSpec Parse(string name, ref int p, bool is_recurse, bool allow_aqn)
+ {
+ // Invariants:
+ // - On exit p, is updated to pos the current unconsumed character.
+ //
+ // - The callee peeks at but does not consume delimiters following
+ // recurisve parse (so for a recursive call like the args of "Foo[P,Q]"
+ // we'll return with p either on ',' or on ']'. If the name was aqn'd
+ // "Foo[[P,assmblystuff],Q]" on return p with be on the ']' just
+ // after the "assmblystuff")
+ //
+ // - If allow_aqn is True, assembly qualification is optional.
+ // If allow_aqn is False, assembly qualification is prohibited.
+ int pos = p;
+ int name_start;
+ bool in_modifiers = false;
+ ClrTypeSpec data = new();
+
+ SkipSpace(name, ref pos);
+
+ name_start = pos;
+
+ for (; pos < name.Length; ++pos)
+ {
+ switch (name[pos])
+ {
+ case '+':
+ data.AddName(name.Substring(name_start, pos - name_start));
+ name_start = pos + 1;
+ break;
+ case ',':
+ case ']':
+ data.AddName(name.Substring(name_start, pos - name_start));
+ name_start = pos + 1;
+ in_modifiers = true;
+ if (is_recurse && !allow_aqn)
+ {
+ p = pos;
+ return data;
+ }
+ break;
+ case '&':
+ case '*':
+ case '[':
+ if (name[pos] != '[' && is_recurse)
+ throw new ArgumentException("Generic argument can't be byref or pointer type", "typeName");
+ data.AddName(name.Substring(name_start, pos - name_start));
+ name_start = pos + 1;
+ in_modifiers = true;
+ break;
+ case '\\':
+ pos++;
+ break;
+ }
+ if (in_modifiers)
+ break;
+ }
+
+ if (name_start < pos)
+ data.AddName(name.Substring(name_start, pos - name_start));
+ else if (name_start == pos)
+ data.AddName(String.Empty);
+
+ if (in_modifiers)
+ {
for (; pos < name.Length; ++pos)
{
+
switch (name[pos])
{
- case '+':
- spec.AddName(name.Substring(name_start, pos - name_start));
- name_start = pos + 1;
+ case '&':
+ if (data._isByRef)
+ throw new ArgumentException("Can't have a byref of a byref", "typeName");
+
+ data._isByRef = true;
+ break;
+ case '*':
+ if (data._isByRef)
+ throw new ArgumentException("Can't have a pointer to a byref type", "typeName");
+ // take subsequent '*'s too
+ int pointer_level = 1;
+ while (pos + 1 < name.Length && name[pos + 1] == '*')
+ {
+ ++pos;
+ ++pointer_level;
+ }
+ data.AddModifier(new ClrPointerSpec(pointer_level));
break;
case ',':
- case ']':
- spec.AddName(name.Substring(name_start, pos - name_start));
- name_start = pos + 1;
- if (isRecursive && !allowAssyQualName)
+ if (is_recurse && allow_aqn)
+ {
+ int end = pos;
+ while (end < name.Length && name[end] != ']')
+ ++end;
+ if (end >= name.Length)
+ throw new ArgumentException("Unmatched ']' while parsing generic argument assembly name");
+ data._assemblyName = name.Substring(pos + 1, end - pos - 1).Trim();
+ p = end;
+ return data;
+ }
+ if (is_recurse)
{
p = pos;
- return spec;
+ return data;
+ }
+ if (allow_aqn)
+ {
+ data._assemblyName = name.Substring(pos + 1).Trim();
+ pos = name.Length;
}
- hasModifiers = true;
break;
- case '&':
- case '*':
case '[':
- if (name[pos] != '[' && isRecursive)
- return null; // ArgumentException ("Generic argument can't be byref or pointer type", "typeName");
- spec.AddName(name.Substring(name_start, pos - name_start));
- name_start = pos + 1;
- hasModifiers = true;
- break;
- case '\\':
- pos++;
- break;
- }
- if (hasModifiers)
- break;
- }
-
- if (name_start < pos)
- spec.AddName(name.Substring(name_start, pos - name_start));
-
- if (hasModifiers)
- {
- for (; pos < name.Length; ++pos)
- {
-
- switch (name[pos])
- {
- case '&':
- if (spec._isByRef)
- return null; // ArgumentException ("Can't have a byref of a byref", "typeName")
-
- spec._isByRef = true;
- break;
- case '*':
- if (spec._isByRef)
- return null; // ArgumentException ("Can't have a pointer to a byref type", "typeName");
- ++spec._pointerLevel;
- break;
- case ',':
- if (isRecursive)
+ if (data._isByRef)
+ throw new ArgumentException("Byref qualifier must be the last one of a type", "typeName");
+ ++pos;
+ if (pos >= name.Length)
+ throw new ArgumentException("Invalid array/generic spec", "typeName");
+ SkipSpace(name, ref pos);
+
+ if (name[pos] != ',' && name[pos] != '*' && name[pos] != ']')
+ {//generic args
+ List args = new();
+ if (data.HasModifiers)
+ throw new ArgumentException("generic args after array spec or pointer type", "typeName");
+
+ while (pos < name.Length)
{
- int end = pos;
- while (end < name.Length && name[end] != ']')
- ++end;
- if (end >= name.Length)
- return null; // ArgumentException ("Unmatched ']' while parsing generic argument assembly name");
- spec._assemblyName = name.Substring(pos + 1, end - pos - 1).Trim();
- p = end + 1;
- return spec;
- }
- spec._assemblyName = name.Substring(pos + 1).Trim();
- pos = name.Length;
- break;
- case '[':
- if (spec._isByRef)
- return null; // ArgumentException ("Byref qualifier must be the last one of a type", "typeName");
- ++pos;
- if (pos >= name.Length)
- return null; // ArgumentException ("Invalid array/generic spec", "typeName");
- SkipSpace(name, ref pos);
-
- if (name[pos] != ',' && name[pos] != '*' && name[pos] != ']')
- {//generic args
- List args = new();
- if (spec.IsArray)
- return null; // ArgumentException ("generic args after array spec", "typeName");
-
- while (pos < name.Length)
+ SkipSpace(name, ref pos);
+ bool aqn = name[pos] == '[';
+ if (aqn)
+ ++pos; //skip '[' to the start of the type
+ args.Add(Parse(name, ref pos, true, aqn));
+ BoundCheck(pos, name);
+ if (aqn)
{
- SkipSpace(name, ref pos);
- bool aqn = name[pos] == '[';
- if (aqn)
- ++pos; //skip '[' to the start of the type
- {
- ClrTypeSpec arg = Parse(name, ref pos, true, aqn);
- if (arg == null)
- return null; // bad generic arg
- args.Add(arg);
- }
- if (pos >= name.Length)
- return null; // ArgumentException ("Invalid generic arguments spec", "typeName");
-
if (name[pos] == ']')
- break;
- if (name[pos] == ',')
- ++pos; // skip ',' to the start of the next arg
+ ++pos;
else
- return null; // ArgumentException ("Invalid generic arguments separator " + name [pos], "typeName")
-
+ throw new ArgumentException("Unclosed assembly-qualified type name at " + name[pos], "typeName"); // Is this possible? AQN Ends with ] pending.
+ BoundCheck(pos, name);
}
- if (pos >= name.Length || name[pos] != ']')
- return null; // ArgumentException ("Error parsing generic params spec", "typeName");
- spec._genericParams = args;
+
+ if (name[pos] == ']')
+ break;
+ if (name[pos] == ',')
+ ++pos; // skip ',' to the start of the next arg
+ else
+ throw new ArgumentException("Invalid generic arguments separator " + name[pos], "typeName");
+
}
- else
- { //array spec
- int dimensions = 1;
- bool bound = false;
- while (pos < name.Length && name[pos] != ']')
+ if (pos >= name.Length || name[pos] != ']')
+ throw new ArgumentException("Error parsing generic params spec", "typeName");
+ data._genericParams = args;
+ }
+ else
+ { //array spec
+ int dimensions = 1;
+ bool bound = false;
+ while (pos < name.Length && name[pos] != ']')
+ {
+ if (name[pos] == '*')
{
- if (name[pos] == '*')
- {
- if (bound)
- return null; // ArgumentException ("Array spec cannot have 2 bound dimensions", "typeName");
- bound = true;
- }
- else if (name[pos] != ',')
- return null; // ArgumentException ("Invalid character in array spec " + name [pos], "typeName");
- else
- ++dimensions;
-
- ++pos;
- SkipSpace(name, ref pos);
+ if (bound)
+ throw new ArgumentException("Array spec cannot have 2 bound dimensions", "typeName");
+ bound = true;
}
- if (name[pos] != ']')
- return null; // ArgumentException ("Error parsing array spec", "typeName");
- if (dimensions > 1 && bound)
- return null; // ArgumentException ("Invalid array spec, multi-dimensional array cannot be bound", "typeName")
- spec.AddArray(new ClrArraySpec(dimensions, bound));
- }
+ else if (name[pos] != ',')
+ throw new ArgumentException("Invalid character in array spec " + name[pos], "typeName");
+ else
+ ++dimensions;
- break;
- case ']':
- if (isRecursive)
- {
- p = pos + 1;
- return spec;
+ ++pos;
+ SkipSpace(name, ref pos);
}
- return null; // ArgumentException ("Unmatched ']'", "typeName");
- default:
- return null; // ArgumentException ("Bad type def, can't handle '" + name [pos]+"'" + " at " + pos, "typeName");
- }
+ if (pos >= name.Length || name[pos] != ']')
+ throw new ArgumentException("Error parsing array spec", "typeName");
+ if (dimensions > 1 && bound)
+ throw new ArgumentException("Invalid array spec, multi-dimensional array cannot be bound", "typeName");
+ data.AddModifier(new ClrArraySpec(dimensions, bound));
+ }
+
+ break;
+ case ']':
+ if (is_recurse)
+ {
+ p = pos;
+ return data;
+ }
+ throw new ArgumentException("Unmatched ']'", "typeName");
+ default:
+ throw new ArgumentException("Bad type def, can't handle '" + name[pos] + "'" + " at " + pos, "typeName");
}
}
-
- p = pos;
- return spec;
}
+ p = pos;
+ return data;
+ }
- void AddName(string type_name)
- {
- if (_name == null)
- {
- _name = type_name;
- }
- else
- {
- if (_nested == null)
- _nested = new List();
- _nested.Add(type_name);
- }
- }
+ #endregion
- static void SkipSpace(string name, ref int pos)
- {
- int p = pos;
- while (p < name.Length && Char.IsWhiteSpace(name[p]))
- ++p;
- pos = p;
- }
+ #region Resolving
- bool IsArray
- {
- get { return _arraySpec != null; }
- }
+ // We have to get rid of all references to StackCrawlMark -- just not something we have access to.
- void AddArray(ClrArraySpec array)
- {
- if (_arraySpec == null)
- _arraySpec = new List();
- _arraySpec.Add(array);
- }
+ public Type Resolve(
+ Func assemblyResolver,
+ Func typeResolver,
+ bool throwOnError,
+ bool ignoreCase /*, ref System.Threading.StackCrawlMark stackMark*/)
+ {
+ Assembly asm = null;
- #endregion
+ //if (assemblyResolver == null && typeResolver == null)
+ // return RuntimeType.GetType(DisplayFullName, throwOnError, ignoreCase, false, ref stackMark);
+ // We don't have access to RuntimeType, so we just punt. We will always call with one or the other of assemblyResolver or typeResolver.
- #region Resolving
+ if (assemblyResolver is null && typeResolver is null)
+ throw new ArgumentException("At least one of assemblyResolver or typeResolver must be non-null");
- internal Type Resolve(Func assemblyResolver, Func typeResolver)
+ if (_assemblyName != null)
{
- Assembly asm = null;
+ if (assemblyResolver != null)
+ asm = assemblyResolver(new AssemblyName(_assemblyName));
+ else
+ asm = Assembly.Load(_assemblyName);
- if (_assemblyName != null)
+ if (asm == null)
{
- if (assemblyResolver != null)
- asm = assemblyResolver(new AssemblyName(_assemblyName));
- else
- asm = Assembly.Load(_assemblyName);
-
- if (asm == null)
- return null;
+ if (throwOnError)
+ throw new FileNotFoundException("Could not resolve assembly '" + _assemblyName + "'");
+ return null;
}
+ }
- Type type = typeResolver(asm, _name);
- if (type == null)
- return null;
+ Type type = null;
+ if (typeResolver is not null)
+ type = typeResolver(asm, _name.DisplayName, ignoreCase);
+ else
+ type = asm.GetType(_name.DisplayName, false, ignoreCase);
- if (_nested != null)
+ if (type is null)
+ {
+ if (throwOnError)
+ throw new TypeLoadException("Could not resolve type '" + _name + "'");
+ return null;
+ }
+
+ if (_nested != null)
+ {
+ foreach (var n in _nested)
{
- foreach (var n in _nested)
+ var tmp = type.GetNestedType(n.DisplayName, BindingFlags.Public | BindingFlags.NonPublic);
+ if (tmp == null)
{
- var tmp = type.GetNestedType(n, BindingFlags.Public | BindingFlags.NonPublic);
- if (tmp == null)
- return null;
- type = tmp;
+ if (throwOnError)
+ throw new TypeLoadException("Could not resolve type '" + n + "'");
+ return null;
}
+ type = tmp;
}
+ }
- if (_genericParams != null)
+ if (_genericParams != null)
+ {
+ Type[] args = new Type[_genericParams.Count];
+ for (int i = 0; i < args.Length; ++i)
{
- Type[] args = new Type[_genericParams.Count];
- for (int i = 0; i < args.Length; ++i)
+ var tmp = _genericParams[i].Resolve(assemblyResolver, typeResolver, throwOnError, ignoreCase /*, ref stackMark */);
+ if (tmp == null)
{
- var tmp = _genericParams[i].Resolve(assemblyResolver, typeResolver);
- if (tmp == null)
- return null;
- args[i] = tmp;
+ if (throwOnError)
+ throw new TypeLoadException("Could not resolve type '" + _genericParams[i]._name + "'");
+ return null;
}
- type = type.MakeGenericType(args);
+ args[i] = tmp;
}
+ type = type.MakeGenericType(args);
+ }
- if (_arraySpec != null)
- {
- foreach (var arr in _arraySpec)
- type = arr.Resolve(type);
- }
+ if (_modifierSpec != null)
+ {
+ foreach (var md in _modifierSpec)
+ type = md.Resolve(type);
+ }
- for (int i = 0; i < _pointerLevel; ++i)
- type = type.MakePointerType();
+ if (_isByRef)
+ type = type.MakeByRefType();
- if (_isByRef)
- type = type.MakeByRefType();
+ return type;
+ }
+
+ #endregion
- return type;
- }
- #endregion
+ #region Entry point
+
+ public static Type GetTypeFromName(string name)
+ {
+ ClrTypeSpec spec = Parse(name);
+ if (spec == null)
+ return null;
+ return spec.Resolve(
+ assyName => Assembly.Load(assyName),
+ //(assy, typeName) => assy == null ? RT.classForName(typeName) : assy.GetType(typeName)); <--- this goes into an infinite loop on a non-existent typename
+ (assy, typeName, ignoreCase) => assy == null ? (name.Equals(typeName) ? null : RT.classForName(typeName)) : assy.GetType(typeName),
+ false,
+ false);
}
+
+ #endregion
+
}
diff --git a/Clojure/Clojure/Lib/ClrTypeSpec2.cs b/Clojure/Clojure/Lib/ClrTypeSpec2.cs
index 1725dd6e..852316e8 100644
--- a/Clojure/Clojure/Lib/ClrTypeSpec2.cs
+++ b/Clojure/Clojure/Lib/ClrTypeSpec2.cs
@@ -1,424 +1,1307 @@
-using clojure.lang.CljCompiler.Ast;
-using System;
+using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Reflection;
+using System.Text;
+
+namespace clojure.lang.TypeName2;
+
+// Inspired by similar code in various places
+// See https://github.com/mono/mono/blob/master/mcs/class/corlib/System/TypeSpec.cs
+// and see http://www.java2s.com/Open-Source/ASP.NET/Library/sixpack-library/SixPack/Reflection/TypeName.cs.htm
+// The EBNF for fully-qualified type names is here: http://msdn.microsoft.com/en-us/library/yfsftwz6(v=VS.100).aspx
+// I primarily followed the mono version. Modifications have to do with assembly and type resolver defaults and some minor details.
+// Also, rather than throwing exceptions for badly formed names, we just return null. Where this is called, generally, an error is not required.
+//
+// Giving credit where credit is due: please note the following attributions in the mono code:
+//
+// Author:
+// Rodrigo Kumpera
+//
+// Copyright (C) 2010 Novell, Inc (http://www.novell.com)
+//
+// Since my initial pull in 2012, Mono made at least seven commits updating this code.
+// 2025.09.11 -- bring this code up to date.
+
+
+// A TypeName is wrapper around type names in display form
+// (that is, with special characters escaped).
+//
+// Note that in general if you unescape a type name, you will
+// lose information: If the type name's DisplayName is
+// Foo\+Bar+Baz (outer class ``Foo+Bar``, inner class Baz)
+// unescaping the first plus will give you (outer class Foo,
+// inner class Bar, innermost class Baz).
+//
+// The correct way to take a TypeName apart is to feed its
+// DisplayName to TypeSpec.Parse()
+//
+public interface IClrTypeName : IEquatable
+{
+ string DisplayName { get; }
+
+ int ImplicitGenericCount { get; set; } // used in some places to track generic arity
+
-namespace clojure.lang
+ // add a nested name under this one.
+ //IClrTypeName NestedName(IClrTypeIdentifier innerName);
+}
+
+// A type identifier is a single component of a type name.
+// Unlike a general typename, a type identifier can be be
+// converted to internal form without loss of information.
+public interface IClrTypeIdentifier : IClrTypeName
{
- // Inspired by similar code in various places
- // See https://github.com/mono/mono/blob/master/mcs/class/corlib/System/TypeSpec.cs
- // and see http://www.java2s.com/Open-Source/ASP.NET/Library/sixpack-library/SixPack/Reflection/TypeName.cs.htm
- // The EBNF for fully-qualified type names is here: http://msdn.microsoft.com/en-us/library/yfsftwz6(v=VS.100).aspx
- // I primarily followed the mono version. Modifications have to do with assembly and type resolver defaults and some minor details.
- // Also, rather than throwing exceptions for badly formed names, we just return null. Where this is called, generally, an error is not required.
- //
- // Giving credit where credit is due: please note the following attributions in the mono code:
- //
- // Author:
- // Rodrigo Kumpera
- //
- // Copyright (C) 2010 Novell, Inc (http://www.novell.com)
+ string InternalName { get; }
+}
- class ClrArraySpec2
+internal class ClrTypeNames
+{
+ internal static IClrTypeName FromDisplay(string displayName)
{
+ return new Display(displayName);
+ }
- #region Data
+ internal abstract class ATypeName : IClrTypeName
+ {
+ public abstract string DisplayName { get; }
+ public int ImplicitGenericCount { get; set; } = 0;
- private readonly int _dimensions;
- private readonly bool _isBound;
+ //public abstract IClrTypeName NestedName(IClrTypeIdentifier innerName);
- #endregion
+ public bool Equals(IClrTypeName other)
+ {
+ return other != null && DisplayName == other.DisplayName;
+ }
- #region C-tors
+ public override int GetHashCode()
+ {
+ return DisplayName.GetHashCode();
+ }
- internal ClrArraySpec2(int dimensions, bool bound)
+ public override bool Equals(object other)
{
- this._dimensions = dimensions;
- this._isBound = bound;
+ return Equals(other as IClrTypeName);
}
+ }
- #endregion
- #region Resolving
+ internal class Display : ATypeName
+ {
+ string _displayName;
- internal Type Resolve(Type type)
+ internal Display(string displayName)
{
- if (_isBound)
- return type.MakeArrayType(1);
- else if (_dimensions == 1)
- return type.MakeArrayType();
- return type.MakeArrayType(_dimensions);
+ this._displayName = displayName;
}
- #endregion
- }
+ public override string DisplayName { get { return _displayName; } }
- public class ClrTypeSpec2
- {
- #region Data
+ //public override IClrTypeName NestedName(IClrTypeIdentifier innerName)
+ //{
+ // return new Display(DisplayName + "+" + innerName.DisplayName);
+ //}
- string _name;
- string _assemblyName;
- List _nested;
- List _genericParams;
- List _arraySpec;
- int _pointerLevel;
- bool _isByRef;
+ }
+}
+
+internal class ClrTypeIdentifiers
+{
- #endregion
+ internal static IClrTypeIdentifier FromDisplay(string displayName)
+ {
+ return new Display(displayName);
+ }
- #region Entry point
+ private class Display : ClrTypeNames.ATypeName, IClrTypeIdentifier
+ {
+ string displayName;
+ string internal_name; //cached
- public static Type GetTypeFromName(string name, Namespace ns = null)
+ internal Display(string displayName)
{
- ClrTypeSpec2 spec = Parse(name);
- if (spec == null)
- return null;
- return spec.Resolve(
- ns,
- name,
- assyName => Assembly.Load(assyName));
+ this.displayName = displayName;
+ internal_name = null;
}
- #endregion
-
- #region Parsing
+ public override string DisplayName
+ {
+ get { return displayName; }
+ }
- static ClrTypeSpec2 Parse(string name)
+ public string InternalName
{
- int pos = 0;
- ClrTypeSpec2 spec = Parse(name, ref pos, false, false);
- if (spec == null)
- return null; // bad parse
- if (pos < name.Length)
- return null; // ArgumentException ("Count not parse the whole type name", "typeName");
- return spec;
+ get
+ {
+ if (internal_name == null)
+ internal_name = GetInternalName();
+ return internal_name;
+ }
}
- static ClrTypeSpec2 Parse(string name, ref int p, bool isRecursive, bool allowAssyQualName)
+ private string GetInternalName()
{
- int pos = p;
- int name_start;
- bool hasModifiers = false;
- ClrTypeSpec2 spec = new();
+ return ClrTypeSpec.UnescapeInternalName(displayName);
+ }
- SkipSpace(name, ref pos);
+ //public override IClrTypeName NestedName(IClrTypeIdentifier innerName)
+ //{
+ // return ClrTypeNames.FromDisplay(DisplayName + "+" + innerName.DisplayName);
+ //}
+ }
+}
- name_start = pos;
+public interface IClrModifierSpec
+{
+ Type Resolve(Type type);
+ StringBuilder Append(StringBuilder sb);
+}
- for (; pos < name.Length; ++pos)
- {
- switch (name[pos])
- {
- case '+':
- spec.AddName(name.Substring(name_start, pos - name_start));
- name_start = pos + 1;
- break;
- case ',':
- case ']':
- spec.AddName(name.Substring(name_start, pos - name_start));
- name_start = pos + 1;
- if (isRecursive && !allowAssyQualName)
- {
- p = pos;
- return spec;
- }
- hasModifiers = true;
- break;
- case '&':
- case '*':
- case '[':
- if (name[pos] != '[' && name[pos] != '<' && isRecursive)
- return null; // ArgumentException ("Generic argument can't be byref or pointer type", "typeName");
- spec.AddName(name.Substring(name_start, pos - name_start));
- name_start = pos + 1;
- hasModifiers = true;
- break;
- case '\\':
- pos++;
- break;
- }
- if (hasModifiers)
- break;
- }
+public class ClrArraySpec : IClrModifierSpec
+{
- if (name_start < pos)
- spec.AddName(name.Substring(name_start, pos - name_start));
+ // dimensions == 1 and bound, or dimensions > 1 and !bound
+ private readonly int _dimensions;
+ private readonly bool _isBound;
- if (hasModifiers)
- {
- for (; pos < name.Length; ++pos)
- {
+ public ClrArraySpec(int dimensions, bool bound)
+ {
+ this._dimensions = dimensions;
+ this._isBound = bound;
+ }
- switch (name[pos])
- {
- case '&':
- if (spec._isByRef)
- return null; // ArgumentException ("Can't have a byref of a byref", "typeName")
-
- spec._isByRef = true;
- break;
- case '*':
- if (spec._isByRef)
- return null; // ArgumentException ("Can't have a pointer to a byref type", "typeName");
- ++spec._pointerLevel;
- break;
- case ',':
- if (isRecursive)
- {
- int end = pos;
- while (end < name.Length && name[end] != ']')
- ++end;
- if (end >= name.Length)
- return null; // ArgumentException ("Unmatched ']' while parsing generic argument assembly name");
- spec._assemblyName = name.Substring(pos + 1, end - pos - 1).Trim();
- p = end + 1;
- return spec;
- }
- spec._assemblyName = name.Substring(pos + 1).Trim();
- pos = name.Length;
- break;
- case '[':
- if (spec._isByRef)
- return null; // ArgumentException ("Byref qualifier must be the last one of a type", "typeName");
- ++pos;
- if (pos >= name.Length)
- return null; // ArgumentException ("Invalid array/generic spec", "typeName");
- SkipSpace(name, ref pos);
+ public override bool Equals(object obj)
+ {
+ var o = obj as ClrArraySpec;
+ if (o == null)
+ return false;
+ return o._dimensions == _dimensions && o._isBound == _isBound;
+ }
- if (name[pos] != ',' && name[pos] != '*' && name[pos] != ']')
- {//generic args
- List args = new();
- if (spec.IsArray)
- return null; // ArgumentException ("generic args after array spec", "typeName");
+ public override int GetHashCode()
+ {
+ return 37 * _dimensions.GetHashCode() + IsBound.GetHashCode();
+ }
- while (pos < name.Length)
- {
- SkipSpace(name, ref pos);
- bool aqn = name[pos] == '[';
- if (aqn)
- ++pos; //skip '[' to the start of the type
- {
- ClrTypeSpec2 arg = Parse(name, ref pos, true, aqn);
- if (arg == null)
- return null; // bad generic arg
- args.Add(arg);
- }
- if (pos >= name.Length)
- return null; // ArgumentException ("Invalid generic arguments spec", "typeName");
+ public Type Resolve(Type type)
+ {
+ if (_isBound)
+ return type.MakeArrayType(1);
+ else if (_dimensions == 1)
+ return type.MakeArrayType();
+ return type.MakeArrayType(_dimensions);
+ }
- if (name[pos] == ']')
- break;
- if (name[pos] == ',')
- ++pos; // skip ',' to the start of the next arg
- else
- return null; // ArgumentException ("Invalid generic arguments separator " + name [pos], "typeName")
+ public StringBuilder Append(StringBuilder sb)
+ {
+ if (_isBound)
+ return sb.Append("[*]");
+ return sb.Append('[')
+ .Append(',', _dimensions - 1)
+ .Append(']');
+ }
- }
- if (pos >= name.Length || name[pos] != ']')
- return null; // ArgumentException ("Error parsing generic params spec", "typeName");
- spec._genericParams = args;
- }
- else
- { //array spec
- int dimensions = 1;
- bool bound = false;
- while (pos < name.Length && name[pos] != ']')
- {
- if (name[pos] == '*')
- {
- if (bound)
- return null; // ArgumentException ("Array spec cannot have 2 bound dimensions", "typeName");
- bound = true;
- }
- else if (name[pos] != ',')
- return null; // ArgumentException ("Invalid character in array spec " + name [pos], "typeName");
- else
- ++dimensions;
+ public override string ToString() => Append(new StringBuilder()).ToString();
- ++pos;
- SkipSpace(name, ref pos);
- }
- if (name[pos] != ']')
- return null; // ArgumentException ("Error parsing array spec", "typeName");
- if (dimensions > 1 && bound)
- return null; // ArgumentException ("Invalid array spec, multi-dimensional array cannot be bound", "typeName")
- spec.AddArray(new ClrArraySpec2(dimensions, bound));
- }
+ public int Rank => _dimensions;
- break;
+ public bool IsBound => _isBound;
- case ']':
- if (isRecursive)
- {
- p = pos + 1;
- return spec;
- }
- return null; // ArgumentException ("Unmatched ']'", "typeName");
- default:
- return null; // ArgumentException ("Bad type def, can't handle '" + name [pos]+"'" + " at " + pos, "typeName");
- }
- }
- }
- p = pos;
- return spec;
+}
+
+public class ClrPointerSpec : IClrModifierSpec
+{
+ int pointer_level;
+
+ public ClrPointerSpec(int pointer_level)
+ {
+ this.pointer_level = pointer_level;
+ }
+
+ public override bool Equals(object obj)
+ {
+ var o = obj as ClrPointerSpec;
+ if (o == null)
+ return false;
+ return o.pointer_level == pointer_level;
+ }
+
+ override public int GetHashCode() => pointer_level.GetHashCode();
+
+ public Type Resolve(Type type)
+ {
+ for (int i = 0; i < pointer_level; ++i)
+ type = type.MakePointerType();
+ return type;
+ }
+
+ public StringBuilder Append(StringBuilder sb) => sb.Append('*', pointer_level);
+
+ public override string ToString() => Append(new StringBuilder()).ToString();
+}
+
+public class ClrTypeSpec
+{
+ #region Data
+
+ IClrTypeIdentifier _name;
+ string _assemblyName;
+ List _nested;
+ List _genericParams;
+ List _modifierSpec;
+ bool _isByRef;
+
+ string _displayFullname; // cache
+
+ #endregion
+
+ #region Accessors
+
+ public bool HasModifiers => _modifierSpec is not null;
+ public bool IsNested => _nested is not null && _nested.Count > 0;
+ public bool IsByRef => _isByRef;
+ public IClrTypeName Name => _name;
+ public string AssemblyName => _assemblyName;
+
+ public IEnumerable Nested
+ {
+ get
+ {
+ if (_nested != null)
+ return _nested;
+ else
+ return Array.Empty();
}
+ }
+ public IEnumerable Modifiers
+ {
+ get
+ {
+ if (_modifierSpec != null)
+ return _modifierSpec;
+ else
+ return Array.Empty();
+ }
+ }
- void AddName(string type_name)
+ public IEnumerable GenericParams
+ {
+ get
{
- if (_name == null)
- {
- _name = type_name;
- }
+ if (_genericParams != null)
+ return _genericParams;
else
+ return Array.Empty();
+ }
+ }
+
+ #endregion
+
+ #region Display name-related
+
+ [Flags]
+ internal enum DisplayNameFormat
+ {
+ Default = 0x0,
+ WANT_ASSEMBLY = 0x1,
+ NO_MODIFIERS = 0x2,
+ }
+
+ //#if DEBUG
+ public override string ToString()
+ {
+ return GetDisplayFullName(DisplayNameFormat.WANT_ASSEMBLY);
+ }
+ //#endif
+
+ string GetDisplayFullName(DisplayNameFormat flags)
+ {
+ bool wantAssembly = (flags & DisplayNameFormat.WANT_ASSEMBLY) != 0;
+ bool wantModifiers = (flags & DisplayNameFormat.NO_MODIFIERS) == 0;
+
+ var sb = new StringBuilder(_name.DisplayName);
+
+ if (_nested is not null)
+ {
+ foreach (var n in _nested)
+ sb.Append('+').Append(n.DisplayName);
+ }
+
+ if (_genericParams is not null)
+ {
+ sb.Append('[');
+ for (int i = 0; i < _genericParams.Count; ++i)
{
- if (_nested == null)
- _nested = new List();
- _nested.Add(type_name);
+ if (i > 0)
+ sb.Append(", ");
+ if (_genericParams[i]._assemblyName != null)
+ sb.Append('[').Append(_genericParams[i].DisplayFullName).Append(']');
+ else
+ sb.Append(_genericParams[i].DisplayFullName);
}
+ sb.Append(']');
}
- static string AppendGenericCountSuffix(string name, int count)
+ if (wantModifiers)
+ GetModifierString(sb);
+
+ if (_assemblyName != null && wantAssembly)
+ sb.Append(", ").Append(_assemblyName);
+
+ return sb.ToString();
+ }
+
+ internal string ModifierString() => GetModifierString(new StringBuilder()).ToString();
+
+ private StringBuilder GetModifierString(StringBuilder sb)
+ {
+ if (_modifierSpec is not null)
+ {
+ foreach (var md in _modifierSpec)
+ md.Append(sb);
+ }
+
+ if (_isByRef)
+ sb.Append('&');
+
+ return sb;
+ }
+
+ internal string DisplayFullName
+ {
+ get
{
- return $"{name}`{count}";
+ if (_displayFullname is null)
+ _displayFullname = GetDisplayFullName(DisplayNameFormat.Default);
+ return _displayFullname;
}
+ }
- void AppendNameGenericCountSuffix(int count)
+ internal static string EscapeDisplayName(string internalName)
+ {
+ // initial capacity = length of internalName.
+ // Maybe we won't have to escape anything.
+ var res = new StringBuilder(internalName.Length);
+ foreach (char c in internalName)
{
- if (_nested is not null)
+ switch (c)
{
- var name = _nested.Last();
- _nested.RemoveAt(_nested.Count - 1);
- _nested.Add(AppendGenericCountSuffix(name, count));
+ case '+':
+ case ',':
+ case '[':
+ case ']':
+ case '*':
+ case '&':
+ case '\\':
+ res.Append('\\').Append(c);
+ break;
+ default:
+ res.Append(c);
+ break;
}
- else
+ }
+ return res.ToString();
+ }
+
+ internal static string UnescapeInternalName(string displayName)
+ {
+ var res = new StringBuilder(displayName.Length);
+ for (int i = 0; i < displayName.Length; ++i)
+ {
+ char c = displayName[i];
+ if (c == '\\')
+ if (++i < displayName.Length)
+ c = displayName[i];
+ res.Append(c);
+ }
+ return res.ToString();
+ }
+
+ internal static bool NeedsEscaping(string internalName)
+ {
+ foreach (char c in internalName)
+ {
+ switch (c)
{
- _name = AppendGenericCountSuffix(_name, count);
+ case ',':
+ case '+':
+ case '*':
+ case '&':
+ case '[':
+ case ']':
+ case '\\':
+ return true;
+ default:
+ break;
}
}
+ return false;
+ }
+
+ #endregion
+
+ #region Parsing support
- static void SkipSpace(string name, ref int pos)
+ void AddName(string type_name)
+ {
+ if (_name is null)
{
- int p = pos;
- while (p < name.Length && Char.IsWhiteSpace(name[p]))
- ++p;
- pos = p;
+ _name = ParsedTypeIdentifier(type_name);
}
+ else
+ {
+ if (_nested == null)
+ _nested = new List();
+ _nested.Add(ParsedTypeIdentifier(type_name));
+ }
+ }
+
+ void AddModifier(IClrModifierSpec md)
+ {
+ if (_modifierSpec is null)
+ _modifierSpec = new List();
+ _modifierSpec.Add(md);
+ }
+
+ static void SkipSpace(string name, ref int pos)
+ {
+ int p = pos;
+ while (p < name.Length && Char.IsWhiteSpace(name[p]))
+ ++p;
+ pos = p;
+ }
+
+ static void BoundCheck(int idx, string s)
+ {
+ if (idx >= s.Length)
+ throw new ArgumentException("Invalid generic arguments spec", "typeName");
+ }
+
+ static IClrTypeIdentifier ParsedTypeIdentifier(string displayName)
+ {
+ return ClrTypeIdentifiers.FromDisplay(displayName);
+ }
+
+ void SetGenericArgumentCount(int count)
+ {
+ if (_name == null)
+ throw new InvalidOperationException("Type name not set");
+
+ if (IsNested)
+ _nested.Last().ImplicitGenericCount = count;
+ else
+ _name.ImplicitGenericCount = count;
+ }
+
+ void MergeNested(ClrTypeSpec nestedSpec)
+ {
+
+ // Append all nested names to the current type
+ if (_nested == null)
+ _nested = new List();
+ _nested.Add(nestedSpec._name);
- bool IsArray
+ if (nestedSpec._nested != null)
{
- get { return _arraySpec != null; }
+ _nested.AddRange(nestedSpec._nested);
}
- void AddArray(ClrArraySpec2 array)
+ // append any generic arguments to the current type
+ if (nestedSpec._genericParams != null)
{
- if (_arraySpec == null)
- _arraySpec = new List();
- _arraySpec.Add(array);
+ if (_genericParams == null)
+ _genericParams = new List();
+ _genericParams.AddRange(nestedSpec._genericParams);
+ }
+ // append any modifiers to the current type
+ if (nestedSpec._modifierSpec != null)
+ {
+ if (_modifierSpec == null)
+ _modifierSpec = new List();
+ _modifierSpec.AddRange(nestedSpec._modifierSpec);
}
- #endregion
+ // byref flag
+ if (nestedSpec._isByRef)
+ {
+ if (_isByRef)
+ throw new ArgumentException("Can't have a byref of a byref", "typeName");
+ _isByRef = true;
+ }
+ }
- #region Resolving
- internal Type Resolve(
- Namespace ns,
- string originalTypename,
- Func assemblyResolver)
- {
- Assembly asm = null;
+ #endregion
- if (_assemblyName != null)
- {
- if (assemblyResolver != null)
- asm = assemblyResolver(new AssemblyName(_assemblyName));
- else
- asm = Assembly.Load(_assemblyName);
+ #region Parsing
- if (asm == null)
- return null;
- }
+ public static ClrTypeSpec Parse(string typeName)
+ {
+ int pos = 0;
+ if (typeName == null)
+ throw new ArgumentNullException("typeName");
+
+ ClrTypeSpec res = Parse(typeName, ref pos, false, true, true);
+ if (pos < typeName.Length)
+ throw new ArgumentException("Count not parse the whole type name", "typeName");
+ return res;
+ }
- // if _name is same as originalTypename, then the parse is identical to what we started with.
- // Given that ClrTypeSpec2.GetTypeFromName is called from RT.classForName,
- // call RT.classForName when _name == originalTypename will set off an infinite recrusion.
+ static ClrTypeSpec Parse(string name, ref int p, bool is_recurse, bool allow_aqn, bool allow_mods)
+ {
+ // Invariants:
+ // - On exit p, is updated to pos the current unconsumed character.
+ //
+ // - The callee peeks at but does not consume delimiters following
+ // recurisve parse (so for a recursive call like the args of "Foo[P,Q]"
+ // we'll return with p either on ',' or on ']'. If the name was aqn'd
+ // "Foo[[P,assmblystuff],Q]" on return p with be on the ']' just
+ // after the "assmblystuff")
+ //
+ // - If allow_aqn is True, assembly qualification is optional.
+ // If allow_aqn is False, assembly qualification is prohibited.
+ // - If is_recurse is True, we are parsing a generic argument.
+ // If is_recurse is False, we are parsing a top-level type name.
+ // - If allow_mods is False, we are recursively parsing a nested name just after a generic argument list.
+ // In this case, we allow modifiers (array, pointer, byref) after the generic argument list.
+ int pos = p;
+
+ int name_start;
+ bool in_modifiers = false;
+ ClrTypeSpec data = new();
+
+ SkipSpace(name, ref pos);
+
+ name_start = pos;
+
+ for (; pos < name.Length; ++pos)
+ {
+ switch (name[pos])
+ {
+ case '+':
+ data.AddName(name.Substring(name_start, pos - name_start));
+ name_start = pos + 1;
+ break;
+ case ',':
+ case ']':
+ data.AddName(name.Substring(name_start, pos - name_start));
+ name_start = pos + 1;
+ in_modifiers = true;
+ if (is_recurse && !allow_aqn)
+ {
+ p = pos;
+ return data;
+ }
+ break;
+ case '&':
+ case '*':
+ case '[':
+ if (name[pos] != '[' && is_recurse)
+ throw new ArgumentException("Generic argument can't be byref or pointer type", "typeName");
+ data.AddName(name.Substring(name_start, pos - name_start));
+ name_start = pos + 1;
+ in_modifiers = true;
+ break;
+ case '\\':
+ pos++;
+ break;
+ }
+ if (in_modifiers)
+ break;
+ }
- Type type = null;
+ if (name_start < pos)
+ data.AddName(name.Substring(name_start, pos - name_start));
+ else if (name_start == pos)
+ data.AddName(String.Empty);
- if (asm != null)
- type = asm.GetType(_name);
- else
+ if (in_modifiers)
+ {
+ for (; pos < name.Length; ++pos)
{
- type = HostExpr.maybeSpecialTag(Symbol.create(_name));
- // check for aliases in the namespace
- if (type is null && ns is not null)
+ switch (name[pos])
{
- type = ns.GetMapping(Symbol.create(_name)) as Type;
- }
+ case '&':
+ if (!allow_mods)
+ {
+ p = pos;
+ return data;
+ }
+ if (data._isByRef)
+ throw new ArgumentException("Can't have a byref of a byref", "typeName");
+
+ data._isByRef = true;
+ break;
+ case '*':
+ if (!allow_mods)
+ {
+ p = pos;
+ return data;
+ }
+ if (data._isByRef)
+ throw new ArgumentException("Can't have a pointer to a byref type", "typeName");
+ // take subsequent '*'s too
+ int pointer_level = 1;
+ while (pos + 1 < name.Length && name[pos + 1] == '*')
+ {
+ ++pos;
+ ++pointer_level;
+ }
+ data.AddModifier(new ClrPointerSpec(pointer_level));
+ break;
+ case ',':
+ if (!allow_mods)
+ {
+ p = pos;
+ return data;
+ }
+ if (is_recurse && allow_aqn)
+ {
+ int end = pos;
+ while (end < name.Length && name[end] != ']')
+ ++end;
+ if (end >= name.Length)
+ throw new ArgumentException("Unmatched ']' while parsing generic argument assembly name");
+ data._assemblyName = name.Substring(pos + 1, end - pos - 1).Trim();
+ p = end;
+ return data;
+ }
+ if (is_recurse)
+ {
+ p = pos;
+ return data;
+ }
+ if (allow_aqn)
+ {
+ data._assemblyName = name.Substring(pos + 1).Trim();
+ pos = name.Length;
+ }
+ break;
+ case '[':
+
+ // We need indefinite lookahead (SkipSpace) to figure out if we have generic arguments or an array spec.
+ // We cache the current position so we can restore it in case we need to exit (when array spec && ! allow_mods)
+
+ int pos_cache = pos;
+
+ if (data._isByRef)
+ throw new ArgumentException("Byref qualifier must be the last one of a type", "typeName");
+ ++pos;
+ if (pos >= name.Length)
+ throw new ArgumentException("Invalid array/generic spec", "typeName");
+ SkipSpace(name, ref pos);
+
+ if (name[pos] != ',' && name[pos] != '*' && name[pos] != ']')
+ {//generic args
+ List args = new();
+ if (data.HasModifiers)
+ throw new ArgumentException("generic args after array spec or pointer type", "typeName");
+
+ while (pos < name.Length)
+ {
+ SkipSpace(name, ref pos);
+ bool aqn = name[pos] == '[';
+ if (aqn)
+ ++pos; //skip '[' to the start of the type
+ args.Add(Parse(name, ref pos, true, aqn, true));
+ BoundCheck(pos, name);
+ if (aqn)
+ {
+ if (name[pos] == ']')
+ ++pos;
+ else
+ throw new ArgumentException("Unclosed assembly-qualified type name at " + name[pos], "typeName"); // Is this possible? AQN Ends with ] pending.
+ BoundCheck(pos, name);
+ }
+
+ if (name[pos] == ']')
+ break;
+ if (name[pos] == ',')
+ ++pos; // skip ',' to the start of the next arg
+ else
+ throw new ArgumentException("Invalid generic arguments separator " + name[pos], "typeName");
+
+ }
+ if (pos >= name.Length || name[pos] != ']')
+ throw new ArgumentException("Error parsing generic params spec", "typeName");
+ data._genericParams = args;
+ data.SetGenericArgumentCount(args.Count);
+ if (pos + 1 < name.Length && name[pos + 1] == '+')
+ {
+ // We have a nested type after a generic argument list. (Extension to the original syntax.)
+ // Recursively parse to pick up the remainder, then merge the results.
+ pos += 2; // skip "]+" to the start of the nested name
+ var nested = Parse(name, ref pos, true, false, true);
+ data.MergeNested(nested);
+ }
+ }
+ else
+ { //array spec
+
+ if (!allow_mods)
+ {
+ // We have an array spec (a mod) and we are not allowing mods
+ // Backup to the position of the [ and get us out of here.
+ p = pos_cache;
+ return data;
+ }
+
+ int dimensions = 1;
+ bool bound = false;
+ while (pos < name.Length && name[pos] != ']')
+ {
+ if (name[pos] == '*')
+ {
+ if (bound)
+ throw new ArgumentException("Array spec cannot have 2 bound dimensions", "typeName");
+ bound = true;
+ }
+ else if (name[pos] != ',')
+ throw new ArgumentException("Invalid character in array spec " + name[pos], "typeName");
+ else
+ ++dimensions;
+
+ ++pos;
+ SkipSpace(name, ref pos);
+ }
+ if (pos >= name.Length || name[pos] != ']')
+ throw new ArgumentException("Error parsing array spec", "typeName");
+ if (dimensions > 1 && bound)
+ throw new ArgumentException("Invalid array spec, multi-dimensional array cannot be bound", "typeName");
+ data.AddModifier(new ClrArraySpec(dimensions, bound));
+ }
- if (type is null && (!_name?.Equals(originalTypename) ?? false))
- type = RT.classForName(_name);
+ break;
+ case ']':
+ if (is_recurse)
+ {
+ p = pos;
+ return data;
+ }
+ throw new ArgumentException("Unmatched ']'", "typeName");
+ default:
+ throw new ArgumentException("Bad type def, can't handle '" + name[pos] + "'" + " at " + pos, "typeName");
+ }
}
+ }
- if (type is null)
- // Cannot resolve _name
- return null;
+ p = pos;
+ return data;
+ }
- if (_nested != null)
+ #endregion
+
+ #region Resolving
+
+ // We have to get rid of all references to StackCrawlMark -- just not something we have access to.
+
+ public Type Resolve(
+ Func assemblyResolver,
+ Func typeResolver,
+ bool throwOnError,
+ bool ignoreCase /*, ref System.Threading.StackCrawlMark stackMark*/)
+ {
+ Assembly asm = null;
+
+ //if (assemblyResolver == null && typeResolver == null)
+ // return RuntimeType.GetType(DisplayFullName, throwOnError, ignoreCase, false, ref stackMark);
+ // We don't have access to RuntimeType, so we just punt. We will always call with one or the other of assemblyResolver or typeResolver.
+
+ if (assemblyResolver is null && typeResolver is null)
+ throw new ArgumentException("At least one of assemblyResolver or typeResolver must be non-null");
+
+ if (_assemblyName != null)
+ {
+ if (assemblyResolver != null)
+ asm = assemblyResolver(new AssemblyName(_assemblyName));
+ else
+ asm = Assembly.Load(_assemblyName);
+
+ if (asm == null)
{
- foreach (var n in _nested)
- {
- var tmp = type.GetNestedType(n, BindingFlags.Public | BindingFlags.NonPublic);
- if (tmp == null)
- return null;
- type = tmp;
- }
+ if (throwOnError)
+ throw new FileNotFoundException("Could not resolve assembly '" + _assemblyName + "'");
+ return null;
}
+ }
- if (_genericParams != null)
+ Type type = null;
+ if (typeResolver is not null)
+ type = typeResolver(asm, _name.DisplayName, ignoreCase);
+ else
+ type = asm.GetType(_name.DisplayName, false, ignoreCase);
+
+ if (type is null)
+ {
+ if (throwOnError)
+ throw new TypeLoadException("Could not resolve type '" + _name + "'");
+ return null;
+ }
+
+ if (_nested != null)
+ {
+ foreach (var n in _nested)
{
- Type[] args = new Type[_genericParams.Count];
- for (int i = 0; i < args.Length; ++i)
+ var tmp = type.GetNestedType(n.DisplayName, BindingFlags.Public | BindingFlags.NonPublic);
+ if (tmp == null)
{
- var tmp = _genericParams[i].Resolve(ns, originalTypename, assemblyResolver);
- if (tmp == null)
- return null;
- args[i] = tmp;
+ if (throwOnError)
+ throw new TypeLoadException("Could not resolve type '" + n + "'");
+ return null;
}
- type = type.MakeGenericType(args);
+ type = tmp;
}
+ }
- if (_arraySpec != null)
+ if (_genericParams != null)
+ {
+ Type[] args = new Type[_genericParams.Count];
+ for (int i = 0; i < args.Length; ++i)
{
- foreach (var arr in _arraySpec)
- type = arr.Resolve(type);
+ var tmp = _genericParams[i].Resolve(assemblyResolver, typeResolver, throwOnError, ignoreCase /*, ref stackMark */);
+ if (tmp == null)
+ {
+ if (throwOnError)
+ throw new TypeLoadException("Could not resolve type '" + _genericParams[i]._name + "'");
+ return null;
+ }
+ args[i] = tmp;
}
+ type = type.MakeGenericType(args);
+ }
- for (int i = 0; i < _pointerLevel; ++i)
- type = type.MakePointerType();
-
- if (_isByRef)
- type = type.MakeByRefType();
-
- return type;
+ if (_modifierSpec != null)
+ {
+ foreach (var md in _modifierSpec)
+ type = md.Resolve(type);
}
+ if (_isByRef)
+ type = type.MakeByRefType();
- private static Type DefaultTypeResolver(Assembly assembly, string typename, Namespace ns)
- {
- if (assembly is not null)
- assembly.GetType(typename);
+ return type;
+ }
+ #endregion
- //(assy, typeName) => assy == null ? (name.Equals(typeName) ? null : RT.classForName(typeName)) : assy.GetType(typeName)
- return null;
- }
+ #region Entry point
- #endregion
+ public static Type GetTypeFromName(string name)
+ {
+ ClrTypeSpec spec = Parse(name);
+ if (spec == null)
+ return null;
+ return spec.Resolve(
+ assyName => Assembly.Load(assyName),
+ //(assy, typeName) => assy == null ? RT.classForName(typeName) : assy.GetType(typeName)); <--- this goes into an infinite loop on a non-existent typename
+ (assy, typeName, ignoreCase) => assy == null ? (name.Equals(typeName) ? null : RT.classForName(typeName)) : assy.GetType(typeName),
+ false,
+ false);
}
+
+ #endregion
+
}
+
+// private readonly int _dimensions;
+// private readonly bool _isBound;
+
+// #endregion
+
+// #region C-tors
+
+// internal ClrArraySpec2(int dimensions, bool bound)
+// {
+// this._dimensions = dimensions;
+// this._isBound = bound;
+// }
+
+// #endregion
+
+// #region Resolving
+
+// internal Type Resolve(Type type)
+// {
+// if (_isBound)
+// return type.MakeArrayType(1);
+// else if (_dimensions == 1)
+// return type.MakeArrayType();
+// return type.MakeArrayType(_dimensions);
+// }
+
+// #endregion
+// }
+
+// public class ClrTypeSpec2
+// {
+// #region Data
+
+// string _name;
+// string _assemblyName;
+// List _nested;
+// List _genericParams;
+// List _arraySpec;
+// int _pointerLevel;
+// bool _isByRef;
+
+// #endregion
+
+// #region Entry point
+
+// public static Type GetTypeFromName(string name, Namespace ns = null)
+// {
+// ClrTypeSpec2 spec = Parse(name);
+// if (spec == null)
+// return null;
+// return spec.Resolve(
+// ns,
+// name,
+// assyName => Assembly.Load(assyName));
+// }
+
+// #endregion
+
+// #region Parsing
+
+// static ClrTypeSpec2 Parse(string name)
+// {
+// int pos = 0;
+// ClrTypeSpec2 spec = Parse(name, ref pos, false, false);
+// if (spec == null)
+// return null; // bad parse
+// if (pos < name.Length)
+// return null; // ArgumentException ("Count not parse the whole type name", "typeName");
+// return spec;
+// }
+
+// static ClrTypeSpec2 Parse(string name, ref int p, bool isRecursive, bool allowAssyQualName)
+// {
+// int pos = p;
+// int name_start;
+// bool hasModifiers = false;
+// ClrTypeSpec2 spec = new();
+
+// SkipSpace(name, ref pos);
+
+// name_start = pos;
+
+// for (; pos < name.Length; ++pos)
+// {
+// switch (name[pos])
+// {
+// case '+':
+// spec.AddName(name.Substring(name_start, pos - name_start));
+// name_start = pos + 1;
+// break;
+// case ',':
+// case ']':
+// spec.AddName(name.Substring(name_start, pos - name_start));
+// name_start = pos + 1;
+// if (isRecursive && !allowAssyQualName)
+// {
+// p = pos;
+// return spec;
+// }
+// hasModifiers = true;
+// break;
+// case '&':
+// case '*':
+// case '[':
+// if (name[pos] != '[' && name[pos] != '<' && isRecursive)
+// return null; // ArgumentException ("Generic argument can't be byref or pointer type", "typeName");
+// spec.AddName(name.Substring(name_start, pos - name_start));
+// name_start = pos + 1;
+// hasModifiers = true;
+// break;
+// case '\\':
+// pos++;
+// break;
+// }
+// if (hasModifiers)
+// break;
+// }
+
+// if (name_start < pos)
+// spec.AddName(name.Substring(name_start, pos - name_start));
+
+// if (hasModifiers)
+// {
+// for (; pos < name.Length; ++pos)
+// {
+
+// switch (name[pos])
+// {
+// case '&':
+// if (spec._isByRef)
+// return null; // ArgumentException ("Can't have a byref of a byref", "typeName")
+
+// spec._isByRef = true;
+// break;
+// case '*':
+// if (spec._isByRef)
+// return null; // ArgumentException ("Can't have a pointer to a byref type", "typeName");
+// ++spec._pointerLevel;
+// break;
+// case ',':
+// if (isRecursive)
+// {
+// int end = pos;
+// while (end < name.Length && name[end] != ']')
+// ++end;
+// if (end >= name.Length)
+// return null; // ArgumentException ("Unmatched ']' while parsing generic argument assembly name");
+// spec._assemblyName = name.Substring(pos + 1, end - pos - 1).Trim();
+// p = end + 1;
+// return spec;
+// }
+// spec._assemblyName = name.Substring(pos + 1).Trim();
+// pos = name.Length;
+// break;
+// case '[':
+// if (spec._isByRef)
+// return null; // ArgumentException ("Byref qualifier must be the last one of a type", "typeName");
+// ++pos;
+// if (pos >= name.Length)
+// return null; // ArgumentException ("Invalid array/generic spec", "typeName");
+// SkipSpace(name, ref pos);
+
+// if (name[pos] != ',' && name[pos] != '*' && name[pos] != ']')
+// {//generic args
+// List args = new();
+// if (spec.IsArray)
+// return null; // ArgumentException ("generic args after array spec", "typeName");
+
+// while (pos < name.Length)
+// {
+// SkipSpace(name, ref pos);
+// bool aqn = name[pos] == '[';
+// if (aqn)
+// ++pos; //skip '[' to the start of the type
+// {
+// ClrTypeSpec2 arg = Parse(name, ref pos, true, aqn);
+// if (arg == null)
+// return null; // bad generic arg
+// args.Add(arg);
+// }
+// if (pos >= name.Length)
+// return null; // ArgumentException ("Invalid generic arguments spec", "typeName");
+
+// if (name[pos] == ']')
+// break;
+// if (name[pos] == ',')
+// ++pos; // skip ',' to the start of the next arg
+// else
+// return null; // ArgumentException ("Invalid generic arguments separator " + name [pos], "typeName")
+
+// }
+// if (pos >= name.Length || name[pos] != ']')
+// return null; // ArgumentException ("Error parsing generic params spec", "typeName");
+// spec._genericParams = args;
+// }
+// else
+// { //array spec
+// int dimensions = 1;
+// bool bound = false;
+// while (pos < name.Length && name[pos] != ']')
+// {
+// if (name[pos] == '*')
+// {
+// if (bound)
+// return null; // ArgumentException ("Array spec cannot have 2 bound dimensions", "typeName");
+// bound = true;
+// }
+// else if (name[pos] != ',')
+// return null; // ArgumentException ("Invalid character in array spec " + name [pos], "typeName");
+// else
+// ++dimensions;
+
+// ++pos;
+// SkipSpace(name, ref pos);
+// }
+// if (name[pos] != ']')
+// return null; // ArgumentException ("Error parsing array spec", "typeName");
+// if (dimensions > 1 && bound)
+// return null; // ArgumentException ("Invalid array spec, multi-dimensional array cannot be bound", "typeName")
+// spec.AddArray(new ClrArraySpec2(dimensions, bound));
+// }
+
+// break;
+
+// case ']':
+// if (isRecursive)
+// {
+// p = pos + 1;
+// return spec;
+// }
+// return null; // ArgumentException ("Unmatched ']'", "typeName");
+// default:
+// return null; // ArgumentException ("Bad type def, can't handle '" + name [pos]+"'" + " at " + pos, "typeName");
+// }
+// }
+// }
+
+// p = pos;
+// return spec;
+// }
+
+
+// void AddName(string type_name)
+// {
+// if (_name == null)
+// {
+// _name = type_name;
+// }
+// else
+// {
+// if (_nested == null)
+// _nested = new List();
+// _nested.Add(type_name);
+// }
+// }
+
+// static string AppendGenericCountSuffix(string name, int count)
+// {
+// return $"{name}`{count}";
+// }
+
+// void AppendNameGenericCountSuffix(int count)
+// {
+// if (_nested is not null)
+// {
+// var name = _nested.Last();
+// _nested.RemoveAt(_nested.Count - 1);
+// _nested.Add(AppendGenericCountSuffix(name, count));
+// }
+// else
+// {
+// _name = AppendGenericCountSuffix(_name, count);
+// }
+// }
+
+// static void SkipSpace(string name, ref int pos)
+// {
+// int p = pos;
+// while (p < name.Length && Char.IsWhiteSpace(name[p]))
+// ++p;
+// pos = p;
+// }
+
+// bool IsArray
+// {
+// get { return _arraySpec != null; }
+// }
+
+// void AddArray(ClrArraySpec2 array)
+// {
+// if (_arraySpec == null)
+// _arraySpec = new List();
+// _arraySpec.Add(array);
+// }
+
+// #endregion
+
+// #region Resolving
+
+// internal Type Resolve(
+// Namespace ns,
+// string originalTypename,
+// Func assemblyResolver)
+// {
+// Assembly asm = null;
+
+// if (_assemblyName != null)
+// {
+// if (assemblyResolver != null)
+// asm = assemblyResolver(new AssemblyName(_assemblyName));
+// else
+// asm = Assembly.Load(_assemblyName);
+
+// if (asm == null)
+// return null;
+// }
+
+// // if _name is same as originalTypename, then the parse is identical to what we started with.
+// // Given that ClrTypeSpec2.GetTypeFromName is called from RT.classForName,
+// // call RT.classForName when _name == originalTypename will set off an infinite recrusion.
+
+// Type type = null;
+
+// if (asm != null)
+// type = asm.GetType(_name);
+// else
+// {
+// type = HostExpr.maybeSpecialTag(Symbol.create(_name));
+
+// // check for aliases in the namespace
+// if (type is null && ns is not null)
+// {
+// type = ns.GetMapping(Symbol.create(_name)) as Type;
+// }
+
+// if (type is null && (!_name?.Equals(originalTypename) ?? false))
+// type = RT.classForName(_name);
+// }
+
+// if (type is null)
+// // Cannot resolve _name
+// return null;
+
+// if (_nested != null)
+// {
+// foreach (var n in _nested)
+// {
+// var tmp = type.GetNestedType(n, BindingFlags.Public | BindingFlags.NonPublic);
+// if (tmp == null)
+// return null;
+// type = tmp;
+// }
+// }
+
+// if (_genericParams != null)
+// {
+// Type[] args = new Type[_genericParams.Count];
+// for (int i = 0; i < args.Length; ++i)
+// {
+// var tmp = _genericParams[i].Resolve(ns, originalTypename, assemblyResolver);
+// if (tmp == null)
+// return null;
+// args[i] = tmp;
+// }
+// type = type.MakeGenericType(args);
+// }
+
+// if (_arraySpec != null)
+// {
+// foreach (var arr in _arraySpec)
+// type = arr.Resolve(type);
+// }
+
+// for (int i = 0; i < _pointerLevel; ++i)
+// type = type.MakePointerType();
+
+// if (_isByRef)
+// type = type.MakeByRefType();
+
+// return type;
+// }
+
+
+// private static Type DefaultTypeResolver(Assembly assembly, string typename, Namespace ns)
+// {
+// if (assembly is not null)
+// assembly.GetType(typename);
+
+
+
+// //(assy, typeName) => assy == null ? (name.Equals(typeName) ? null : RT.classForName(typeName)) : assy.GetType(typeName)
+// return null;
+// }
+
+// #endregion
+// }
+//}
diff --git a/Clojure/Clojure/Lib/RT.cs b/Clojure/Clojure/Lib/RT.cs
index 6063ca38..7de8ecb5 100644
--- a/Clojure/Clojure/Lib/RT.cs
+++ b/Clojure/Clojure/Lib/RT.cs
@@ -10,6 +10,7 @@
using clojure.lang.CljCompiler.Context;
using clojure.lang.Runtime;
+using clojure.lang.TypeName;
using Microsoft.Scripting.Hosting;
using System;
using System.Collections;
diff --git a/Clojure/Csharp.Tests/TypeNameParsingTests.cs b/Clojure/Csharp.Tests/TypeNameParsingTests.cs
deleted file mode 100644
index 3fca1633..00000000
--- a/Clojure/Csharp.Tests/TypeNameParsingTests.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-using clojure.lang;
-using NUnit.Framework;
-using System;
-using System.Collections.Generic;
-
-namespace Csharp.Tests;
-
-public class TypeA { }
-public class OneG { }
-public class TwoG { }
-public class GenParent
-{
- public class Child
- {
- public class GrandChild
- {
- public class GreatGrandChild
- {
-
- }
- }
- }
-}
-
-
-[TestFixture]
-public class TypeNameParsingTests
-{
- static Namespace _ns;
-
- [OneTimeSetUp]
- public void Setup()
- {
- RT.Init();
-
- _ns = Namespace.findOrCreate(Symbol.intern("Csharp.Tests"));
-
- _ns.importClass(Symbol.intern("TTypeA"), typeof(TypeA));
- _ns.importClass(Symbol.intern("TOneG"), typeof(OneG<>));
- _ns.importClass(Symbol.intern("TTwoG"), typeof(TwoG<,>));
- _ns.importClass(Symbol.intern("TGenParent"), typeof(GenParent<,>));
-
- RT.CurrentNSVar.bindRoot(_ns);
- }
-
- [TestCase("System.String", typeof(string))]
- [TestCase("Csharp.Tests.TypeA", typeof(TypeA))]
- public void NamespaceQualifiedClassName_ParsesCorrectly(string typename, Type expectedType)
- {
- var type = RT.classForName(typename);
- Assert.That(type, Is.EqualTo(expectedType));
- }
-
- [TestCase("int", typeof(int))]
- [TestCase("long", typeof(long))]
- [TestCase("float", typeof(float))]
- [TestCase("double", typeof(double))]
- [TestCase("bool", typeof(bool))]
- [TestCase("char", typeof(char))]
- [TestCase("byte", typeof(byte))]
- [TestCase("uint", typeof(uint))]
- [TestCase("ulong", typeof(ulong))]
- [TestCase("ushort", typeof(ushort))]
- [TestCase("sbyte", typeof(sbyte))]
- [TestCase("ints", typeof(int[]))]
- [TestCase("longs", typeof(long[]))]
- [TestCase("floats", typeof(float[]))]
- [TestCase("doubles", typeof(double[]))]
- [TestCase("bools", typeof(bool[]))]
- [TestCase("chars", typeof(char[]))]
- [TestCase("bytes", typeof(byte[]))]
- [TestCase("uints", typeof(uint[]))]
- [TestCase("ulongs", typeof(ulong[]))]
- [TestCase("ushorts", typeof(ushort[]))]
- [TestCase("sbytes", typeof(sbyte[]))]
- public void ClojureTypeAlias_ParsesCorrectly(string typename, Type expectedType)
- {
- var type = RT.classForName(typename);
- Assert.That(type, Is.EqualTo(expectedType));
- }
-
- [TestCase("TTypeA", typeof(TypeA))]
- [TestCase("TOneG", typeof(OneG<>))]
- [TestCase("TTwoG", typeof(TwoG<,>))]
- [TestCase("TGenParent", typeof(GenParent<,>))]
- [TestCase("String", typeof(string))]
- [TestCase("Int32", typeof(int))]
- public void AliasedTypename_ParsesCorrectly(string typename, Type expectedType)
- {
- var type = RT.classForName(typename);
- Assert.That(type, Is.EqualTo(expectedType));
- }
-
- [TestCase("int[]", typeof(int[]))]
- [TestCase("String[]", typeof(string[]))]
- [TestCase("TTypeA[]", typeof(TypeA[]))]
- [TestCase("Csharp.Tests.TypeA[]", typeof(TypeA[]))]
- public void ArrayType_ParsesCorrectly(string typename, Type expectedType)
- {
- var type = RT.classForName(typename);
- Assert.That(type, Is.EqualTo(expectedType));
- }
-
-
- [TestCase("int*", typeof(int))]
- [TestCase("String*", typeof(String))]
- [TestCase("TTypeA*", typeof(TypeA))]
- [TestCase("Csharp.Tests.TypeA*", typeof(TypeA))]
- public void PointerType_ParsesCorrectly(string typename, Type expectedType)
- {
- var type = RT.classForName(typename);
- Assert.That(type, Is.EqualTo(expectedType.MakePointerType()));
- }
-
- [TestCase("int&", typeof(int))]
- [TestCase("String&", typeof(String))]
- [TestCase("TTypeA&", typeof(TypeA))]
- [TestCase("Csharp.Tests.TypeA&", typeof(TypeA))]
- public void ByRefType_ParsesCorrectly(string typename, Type expectedType)
- {
- var type = RT.classForName(typename);
- Assert.That(type, Is.EqualTo(expectedType.MakeByRefType()));
- }
-
- [TestCase("TOneG[int]", typeof(OneG))]
- [TestCase("TOneG[String]", typeof(OneG))]
- [TestCase("TTwoG[int,String]", typeof(TwoG))]
- public void GenericType_ParsesCorrectly(string typename, Type expectedType)
- {
- var type = RT.classForName(typename);
- Assert.That(type, Is.EqualTo(expectedType));
- }
-
- [TestCase("System.Collections.Generic.Dictionary`2[System.String, System.Collections.Generic.List`1[System.Int64]]",
- typeof(Dictionary>))]
- //[TestCase("Csharp.Tests.TwoG`2[System.Int32, Csharp.Tests.OneG`1[System.String]]", typeof(TwoG>))]
-
- //[TestCase("TTwoG[int, TOneG[String]]", typeof(TwoG>))]
- public void GenericType_ParsesCorrectly1(string typename, Type expectedType)
- {
- var type = RT.classForName(typename);
- Assert.That(type, Is.EqualTo(expectedType));
- }
-
-
- [TestCase("TTwoG[int, TOneG[String]]", typeof(TwoG>))]
- public void GenericType_ParsesCorrectly2(string typename, Type expectedType)
- {
- var type = ClrTypeSpec.GetTypeFromName(typename);
- Assert.That(type, Is.EqualTo(expectedType));
- }
-
-
-}
-
diff --git a/Clojure/Csharp.Tests/TypeNameTests/TypeNameParsingTests.cs b/Clojure/Csharp.Tests/TypeNameTests/TypeNameParsingTests.cs
new file mode 100644
index 00000000..6ef98464
--- /dev/null
+++ b/Clojure/Csharp.Tests/TypeNameTests/TypeNameParsingTests.cs
@@ -0,0 +1,454 @@
+using clojure.lang.TypeName;
+using NUnit.Framework;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Csharp.Tests.TypeNameTests;
+
+public class TypeSpecComparer
+{
+ IClrTypeIdentifier _name;
+ string _assemblyName = null;
+ List _nested = [];
+ List _genericParams = [];
+ List _modifiers = [];
+ bool _isByRef = false;
+
+ class InternalName : IClrTypeIdentifier
+ {
+ public string DisplayName { get; init; }
+
+ public InternalName(string name)
+ {
+ DisplayName = name;
+ }
+
+ public bool Equals(IClrTypeName other)
+ {
+ return other is not null && other.DisplayName == DisplayName;
+ }
+
+ string IClrTypeIdentifier.InternalName => throw new System.NotImplementedException();
+
+ public IClrTypeName NestedName(IClrTypeIdentifier innerName)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+
+
+ public bool SameAs(ClrTypeSpec spec)
+ {
+ if (spec == null)
+ return false;
+ if (!_name.Equals(spec.Name))
+ return false;
+
+ if (!string.Equals(_assemblyName, spec.AssemblyName))
+ return false;
+
+ if (_isByRef != spec.IsByRef)
+ return false;
+
+ var nested = spec.Nested.ToList();
+
+ if (_nested.Count != nested.Count)
+ return false;
+
+ for (int i = 0; i < _nested.Count; i++)
+ if (!_nested[i].Equals(nested[i]))
+ return false;
+
+ var genericParams = spec.GenericParams.ToList();
+
+ if (_genericParams.Count != genericParams.Count)
+ return false;
+
+ for (int i = 0; i < _genericParams.Count; i++)
+ {
+ if (!_genericParams[i].SameAs(genericParams[i]))
+ return false;
+ }
+
+ var modifiers = spec.Modifiers.ToList();
+
+ if (_modifiers.Count != modifiers.Count)
+ return false;
+
+ for (int i = 0; i < _modifiers.Count; i++)
+ if (!_modifiers[i].Equals(modifiers[i]))
+ return false;
+
+ return true;
+ }
+
+ public static TypeSpecComparer Create(string name)
+ {
+ var cmp = new TypeSpecComparer();
+ cmp._name = new InternalName(name);
+
+ return cmp;
+ }
+
+ public TypeSpecComparer WithAssembly(string assemblyName)
+ {
+ _assemblyName = assemblyName;
+ return this;
+ }
+
+ public TypeSpecComparer WithNested(params string[] names)
+ {
+ _nested = names.Select(n => (IClrTypeIdentifier)new InternalName(n)).ToList();
+ return this;
+ }
+
+ public TypeSpecComparer WithGenericParams(params TypeSpecComparer[] specs)
+ {
+ _genericParams = specs.ToList();
+ return this;
+ }
+
+ public TypeSpecComparer WithModifiers(params IClrModifierSpec[] mods)
+ {
+ _modifiers = mods.ToList();
+ return this;
+ }
+
+ public TypeSpecComparer SetIsByRef()
+ {
+ _isByRef = true;
+ return this;
+ }
+}
+
+
+[TestFixture]
+public class TypeNameParsingTests
+{
+ [TestCase("A", "A", "#1")]
+ [TestCase("A.B", "A.B", "#2")]
+ [TestCase("A\\+B", "A\\+B", "#3")]
+ public void BasicName_ParsesCorrectly(string typeName, string expect, string idString)
+ {
+ var spec = ClrTypeSpec.Parse(typeName);
+ var cmp = TypeSpecComparer.Create(expect);
+ Assert.That(cmp.SameAs(spec), Is.True, idString);
+ }
+
+ [Test]
+ public void TypeNameStartsWithSpace_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse(" A.B");
+ var cmp = TypeSpecComparer.Create("A.B");
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void TypeNameWithSpaceAfterComma_ParsesCorrectly()
+ {
+ var cmp = TypeSpecComparer.Create("A")
+ .WithGenericParams(
+ TypeSpecComparer.Create("B"),
+ TypeSpecComparer.Create("C"));
+
+ var spec = ClrTypeSpec.Parse("A[B, C]");
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B,C]");
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void NestedName_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A+B");
+ var cmp = TypeSpecComparer.Create("A").WithNested("B");
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B+C");
+ cmp = TypeSpecComparer.Create("A").WithNested("B", "C");
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void AssemblyName_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A, MyAssembly");
+ var cmp = TypeSpecComparer.Create("A").WithAssembly("MyAssembly");
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B, MyAssembly");
+ cmp = TypeSpecComparer.Create("A").WithNested("B").WithAssembly("MyAssembly");
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void ArraySpec_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A[]");
+ var cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrArraySpec(1, false));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[,,]");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrArraySpec(3, false));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[,][]");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrArraySpec(2, false), new ClrArraySpec(1, false));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[*]");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrArraySpec(1, true));
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void PointerSpec_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A*");
+ var cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrPointerSpec(1));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A**");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrPointerSpec(2));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A*[]");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrPointerSpec(1), new ClrArraySpec(1, false));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A*&");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrPointerSpec(1)).SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void ByRef_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A&");
+ var cmp = TypeSpecComparer.Create("A").SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B&");
+ cmp = TypeSpecComparer.Create("A").WithNested("B").SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A*&");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrPointerSpec(1)).SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[]&");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrArraySpec(1, false)).SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void GenericParams_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A[B]");
+ var cmp = TypeSpecComparer.Create("A")
+ .WithGenericParams(
+ TypeSpecComparer.Create("B"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B,C]");
+ cmp = TypeSpecComparer.Create("A")
+ .WithGenericParams(
+ TypeSpecComparer.Create("B"),
+ TypeSpecComparer.Create("C"));
+
+ Assert.That(cmp.SameAs(spec), Is.True);
+ spec = ClrTypeSpec.Parse("A[B+C,D]");
+ cmp = TypeSpecComparer.Create("A")
+ .WithGenericParams(
+ TypeSpecComparer.Create("B").WithNested("C"),
+ TypeSpecComparer.Create("D"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B,C[D]]");
+ cmp = TypeSpecComparer.Create("A")
+ .WithGenericParams(
+ TypeSpecComparer.Create("B"),
+ TypeSpecComparer.Create("C")
+ .WithGenericParams(
+ TypeSpecComparer.Create("D")));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B[C],D[E,F]]");
+ cmp = TypeSpecComparer.Create("A")
+ .WithGenericParams(
+ TypeSpecComparer.Create("B")
+ .WithGenericParams(
+ TypeSpecComparer.Create("C")),
+ TypeSpecComparer.Create("D")
+ .WithGenericParams(
+ TypeSpecComparer.Create("E"),
+ TypeSpecComparer.Create("F")));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B[C,D[E,F]],G]");
+ cmp = TypeSpecComparer.Create("A")
+ .WithGenericParams(
+ TypeSpecComparer.Create("B")
+ .WithGenericParams(
+ TypeSpecComparer.Create("C"),
+ TypeSpecComparer.Create("D")
+ .WithGenericParams(
+ TypeSpecComparer.Create("E"),
+ TypeSpecComparer.Create("F"))),
+ TypeSpecComparer.Create("G"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B[C,D[E,F]]");
+ cmp = TypeSpecComparer.Create("A")
+ .WithNested("B")
+ .WithGenericParams(
+ TypeSpecComparer.Create("C"),
+ TypeSpecComparer.Create("D")
+ .WithGenericParams(
+ TypeSpecComparer.Create("E"),
+ TypeSpecComparer.Create("F")));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+
+ spec = ClrTypeSpec.Parse("A[ [B, AssemblyB], C, [D, AssemblyD]], AssemblyA");
+ cmp = TypeSpecComparer.Create("A")
+ .WithAssembly("AssemblyA")
+ .WithGenericParams(
+ TypeSpecComparer.Create("B").WithAssembly("AssemblyB"),
+ TypeSpecComparer.Create("C"),
+ TypeSpecComparer.Create("D").WithAssembly("AssemblyD"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ }
+
+ [Test]
+ public void GenericArg_CannotBeByRef()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[B&]"));
+ Assert.That(exn.Message, Does.Contain("Generic argument can't be byref or pointer type"));
+ }
+
+ [Test]
+ public void GenericArg_CannotBePointer()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[B*]"));
+ Assert.That(exn.Message, Does.Contain("Generic argument can't be byref or pointer type"));
+
+ }
+
+ [Test]
+ public void CannotTakeByRefOfByRef()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A&&"));
+ Assert.That(exn.Message, Does.Contain("Can't have a byref of a byref"));
+
+
+
+ }
+
+ [Test]
+ public void CannotHavePointerAfterByRef()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A&*"));
+ Assert.That(exn.Message, Does.Contain("Can't have a pointer to a byref type"));
+ }
+
+ [Test]
+ public void CannotHaveMissingCloseBracketInGenericArgumentAssemblyName()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[[B, AssemblyB"));
+ Assert.That(exn.Message, Does.Contain("Unmatched ']' while parsing generic argument assembly name"));
+ }
+
+ [Test]
+ public void ByRefQualifierMustBeLast()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A&[]"));
+ Assert.That(exn.Message, Does.Contain("Byref qualifier must be the last one of a type"));
+ }
+
+ [Test]
+ public void MissingCharactersAfterLeftBracketIsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A["));
+ Assert.That(exn.Message, Does.Contain("Invalid array/generic spec"));
+ }
+
+ [Test]
+ public void CannotHaveGenericArgsAfterArrayOrPointer()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[][B]"));
+ Assert.That(exn.Message, Does.Contain("generic args after array spec or pointer type"));
+
+ exn = Assert.Throws(() => ClrTypeSpec.Parse("A*[B]"));
+ Assert.That(exn.Message, Does.Contain("generic args after array spec or pointer type"));
+ }
+
+ [Test]
+ public void InvalidGenericArgsSeparator_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[ [B, AssemblyB ] + C ] "));
+ Assert.That(exn.Message, Does.Contain("Invalid generic arguments separator"));
+ }
+
+ [Test]
+ public void ErrorParsingGenericParamsSpec_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[B,"));
+ Assert.That(exn.Message, Does.Contain("Error parsing generic params spec"));
+ }
+
+ [Test]
+ public void TwoBoundDesignatorsInArraySpec_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[**]"));
+ Assert.That(exn.Message, Does.Contain("Array spec cannot have 2 bound dimensions"));
+ }
+
+ [Test]
+ public void InvalidCharacterInArraySpec_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[*!]"));
+ Assert.That(exn.Message, Does.Contain("Invalid character in array spec"));
+ }
+
+ [Test]
+ public void ErrorParsingArraySpec_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[,"));
+ Assert.That(exn.Message, Does.Contain("Error parsing array spec"));
+ }
+
+ [Test]
+ public void CannotHaveBoundAndDimensionTogetherInArraySpec()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[*,]"));
+ Assert.That(exn.Message, Does.Contain("Invalid array spec, multi-dimensional array cannot be bound"));
+ }
+
+
+ [Test]
+ public void UnmatchedRightBracket_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[]]"));
+ Assert.That(exn.Message, Does.Contain("Unmatched ']'"));
+ }
+
+
+ [Test]
+ public void UnknownCharacterInModifiers_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A*!"));
+ Assert.That(exn.Message, Does.Contain("Bad type def"));
+ }
+
+ // I don't know how to trigger this error
+ //[Test]
+ //public void UnclosedAssemblyQualifiedNameInGenericArg_IsBad()
+ //{
+ // var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[ [B, AssemblyB ] ] "));
+ // Assert.That(exn.Message, Does.Contain("Unclosed assembly-qualified type name"));
+ //}
+}
+
diff --git a/Clojure/Csharp.Tests/TypeNameTests/TypeNameResolvingTests.cs b/Clojure/Csharp.Tests/TypeNameTests/TypeNameResolvingTests.cs
new file mode 100644
index 00000000..5ff04a4b
--- /dev/null
+++ b/Clojure/Csharp.Tests/TypeNameTests/TypeNameResolvingTests.cs
@@ -0,0 +1,107 @@
+using clojure.lang.TypeName;
+using NUnit.Framework;
+using System;
+
+namespace Csharp.Tests.TypeNameTests;
+
+
+public class Simple { }
+
+public class Outer
+{
+ public class Inner { }
+}
+
+public class OneG { }
+public class TwoG { }
+
+public class GenParent
+{
+ public class Child
+ {
+ public class GrandChild
+ {
+ public class GreatGrandChild
+ {
+
+ }
+ }
+ }
+}
+
+
+[TestFixture]
+public class TypeNameResolvingTests
+{
+
+ public static Type TR(string typename, bool throwOnError) => Type.GetType(typename, throwOnError);
+
+
+ public static Type Resolve(string typeName)
+ {
+ var spec = ClrTypeSpec.Parse(typeName);
+ return spec?.Resolve(null, (assemblyName, typename, throwOnError) => TR(typename, throwOnError), false, false);
+ }
+
+ [Test]
+ public void SimpleClassName_ResolvesCorrectly()
+ {
+ Assert.That(Resolve("Csharp.Tests.TypeNameTests.Simple"), Is.EqualTo(typeof(Simple)));
+ }
+
+ [Test]
+ public void NestedClassName_ResolvesCorrectly()
+ {
+ Assert.That(Resolve("Csharp.Tests.TypeNameTests.Outer+Inner"), Is.EqualTo(typeof(Outer.Inner)));
+ }
+
+ [Test]
+ public void GenericClassName_ResolvesCorrectly()
+ {
+ Assert.That(Resolve("Csharp.Tests.TypeNameTests.OneG`1[System.String]"), Is.EqualTo(typeof(OneG)));
+ Assert.That(Resolve("Csharp.Tests.TypeNameTests.TwoG`2[System.String,System.Int32]"), Is.EqualTo(typeof(TwoG)));
+ Assert.That(
+ Resolve("Csharp.Tests.TypeNameTests.GenParent`2+Child+GrandChild`1+GreatGrandChild`2[System.String, System.Int32, System.Double, System.String,System.Object]"),
+ Is.EqualTo(typeof(GenParent.Child.GrandChild.GreatGrandChild)));
+ }
+
+ [Test]
+ public void NonExistentType_ResolvesToNull()
+ {
+ Assert.That(Resolve("Csharp.Tests.TypeNameTests.NonExistent"), Is.Null);
+ Assert.That(Resolve("Csharp.Tests.TypeNameTests.Simple+NonExistent"), Is.Null);
+ Assert.That(Resolve("Csharp.Tests.TypeNameTests.OneG`1[Non.Existent]"), Is.Null);
+ }
+
+ [Test]
+ public void PointerType_ResolvesCorrectly()
+ {
+ Assert.That(Resolve("System.String*"), Is.EqualTo(typeof(string).MakePointerType()));
+ Assert.That(Resolve("System.String**"), Is.EqualTo(typeof(string).MakePointerType().MakePointerType()));
+ }
+
+ [Test]
+ public void ByRefType_ResolvesCorrectly()
+ {
+ Assert.That(Resolve("System.String&"), Is.EqualTo(typeof(string).MakeByRefType()));
+ Assert.That(Resolve("System.String*&"), Is.EqualTo(typeof(string).MakePointerType().MakeByRefType()));
+ }
+
+ [Test]
+ public void ArrayType_ResolvesCorrectly()
+ {
+ Assert.That(Resolve("System.String[]"), Is.EqualTo(typeof(string).MakeArrayType()));
+ Assert.That(Resolve("System.String[*]"), Is.EqualTo(typeof(string).MakeArrayType(1)));
+ Assert.That(Resolve("System.String[,]"), Is.EqualTo(typeof(string).MakeArrayType(2)));
+ Assert.That(Resolve("System.String[,,]"), Is.EqualTo(typeof(string).MakeArrayType(3)));
+ }
+
+
+ [Test]
+ public void Everything_ResolvesCorrectly()
+ {
+ Assert.That(
+ Resolve("Csharp.Tests.TypeNameTests.GenParent`2+Child+GrandChild`1+GreatGrandChild`2[System.String, System.Int32, System.Double, System.String,System.Object][]**&"),
+ Is.EqualTo(typeof(GenParent.Child.GrandChild.GreatGrandChild).MakeArrayType().MakePointerType().MakePointerType().MakeByRefType()));
+ }
+}
diff --git a/Clojure/Csharp.Tests/TypeNameTests2/TypeNameParsingTests2.cs b/Clojure/Csharp.Tests/TypeNameTests2/TypeNameParsingTests2.cs
new file mode 100644
index 00000000..451184ff
--- /dev/null
+++ b/Clojure/Csharp.Tests/TypeNameTests2/TypeNameParsingTests2.cs
@@ -0,0 +1,548 @@
+using clojure.lang.TypeName2;
+using NUnit.Framework;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Csharp.Tests.TypeNameTests2;
+
+public class TypeSpecComparer
+{
+ IClrTypeIdentifier _name;
+ string _assemblyName = null;
+ List _nested = [];
+ List _genericParams = [];
+ List _modifiers = [];
+ bool _isByRef = false;
+
+ class InternalName : IClrTypeIdentifier
+ {
+ public string DisplayName { get; init; }
+ public int ImplicitGenericCount { get; set; }
+
+ public InternalName(string name, int genericCount = 0)
+ {
+ DisplayName = name;
+ ImplicitGenericCount = genericCount;
+ }
+
+ public bool Equals(IClrTypeName other)
+ {
+ return other is not null && other.DisplayName == DisplayName && other.ImplicitGenericCount == ImplicitGenericCount;
+ }
+
+ string IClrTypeIdentifier.InternalName => throw new System.NotImplementedException();
+
+ }
+
+
+ public bool SameAs(ClrTypeSpec spec)
+ {
+ if (spec == null)
+ return false;
+
+ if (!_name.Equals(spec.Name))
+ return false;
+
+ if (!string.Equals(_assemblyName, spec.AssemblyName))
+ return false;
+
+ if (_isByRef != spec.IsByRef)
+ return false;
+
+ var nested = spec.Nested.ToList();
+
+ if (_nested.Count != nested.Count)
+ return false;
+
+ for (int i = 0; i < _nested.Count; i++)
+ if (!_nested[i].Equals(nested[i]))
+ return false;
+
+ var genericParams = spec.GenericParams.ToList();
+
+ if (_genericParams.Count != genericParams.Count)
+ return false;
+
+ for (int i = 0; i < _genericParams.Count; i++)
+ {
+ if (!_genericParams[i].SameAs(genericParams[i]))
+ return false;
+ }
+
+ var modifiers = spec.Modifiers.ToList();
+
+ if (_modifiers.Count != modifiers.Count)
+ return false;
+
+ for (int i = 0; i < _modifiers.Count; i++)
+ if (!_modifiers[i].Equals(modifiers[i]))
+ return false;
+
+ return true;
+ }
+
+ public static TypeSpecComparer Create(string name, int genericCount = 0)
+ {
+ var cmp = new TypeSpecComparer();
+ cmp._name = new InternalName(name, genericCount);
+
+ return cmp;
+ }
+
+ public TypeSpecComparer WithAssembly(string assemblyName)
+ {
+ _assemblyName = assemblyName;
+ return this;
+ }
+
+ public TypeSpecComparer WithNested(params string[] names)
+ {
+ _nested = names.Select(n => (IClrTypeIdentifier)new InternalName(n)).ToList();
+ return this;
+ }
+
+ public TypeSpecComparer WithNested(params (string, int)[] namesAndCounts)
+ {
+ _nested = namesAndCounts.Select(n => (IClrTypeIdentifier)new InternalName(n.Item1, n.Item2)).ToList();
+ return this;
+ }
+
+ public TypeSpecComparer WithGenericParams(params TypeSpecComparer[] specs)
+ {
+ _genericParams = specs.ToList();
+ return this;
+ }
+
+ public TypeSpecComparer WithModifiers(params IClrModifierSpec[] mods)
+ {
+ _modifiers = mods.ToList();
+ return this;
+ }
+
+ public TypeSpecComparer SetIsByRef()
+ {
+ _isByRef = true;
+ return this;
+ }
+}
+
+
+[TestFixture]
+public class TypeNameParsingTests
+{
+ [TestCase("A", "A", "#1")]
+ [TestCase("A.B", "A.B", "#2")]
+ [TestCase("A\\+B", "A\\+B", "#3")]
+ public void BasicName_ParsesCorrectly(string typeName, string expect, string idString)
+ {
+ var spec = ClrTypeSpec.Parse(typeName);
+ var cmp = TypeSpecComparer.Create(expect);
+ Assert.That(cmp.SameAs(spec), Is.True, idString);
+ }
+
+ [Test]
+ public void TypeNameStartsWithSpace_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse(" A.B");
+ var cmp = TypeSpecComparer.Create("A.B");
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void TypeNameWithSpaceAfterComma_ParsesCorrectly()
+ {
+ var cmp = TypeSpecComparer.Create("A", 2)
+ .WithGenericParams(
+ TypeSpecComparer.Create("B"),
+ TypeSpecComparer.Create("C"));
+
+ var spec = ClrTypeSpec.Parse("A[B, C]");
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B,C]");
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void NestedName_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A+B");
+ var cmp = TypeSpecComparer.Create("A").WithNested("B");
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B+C");
+ cmp = TypeSpecComparer.Create("A").WithNested("B", "C");
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void AssemblyName_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A, MyAssembly");
+ var cmp = TypeSpecComparer.Create("A").WithAssembly("MyAssembly");
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B, MyAssembly");
+ cmp = TypeSpecComparer.Create("A").WithNested("B").WithAssembly("MyAssembly");
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void ArraySpec_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A[]");
+ var cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrArraySpec(1, false));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[,,]");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrArraySpec(3, false));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[,][]");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrArraySpec(2, false), new ClrArraySpec(1, false));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[*]");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrArraySpec(1, true));
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void PointerSpec_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A*");
+ var cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrPointerSpec(1));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A**");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrPointerSpec(2));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A*[]");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrPointerSpec(1), new ClrArraySpec(1, false));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A*&");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrPointerSpec(1)).SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void ByRef_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A&");
+ var cmp = TypeSpecComparer.Create("A").SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B&");
+ cmp = TypeSpecComparer.Create("A").WithNested("B").SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A*&");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrPointerSpec(1)).SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[]&");
+ cmp = TypeSpecComparer.Create("A").WithModifiers(new ClrArraySpec(1, false)).SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void GenericParams_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A[B]");
+ var cmp = TypeSpecComparer.Create("A", 1)
+ .WithGenericParams(
+ TypeSpecComparer.Create("B"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B,C]");
+ cmp = TypeSpecComparer.Create("A", 2)
+ .WithGenericParams(
+ TypeSpecComparer.Create("B"),
+ TypeSpecComparer.Create("C"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B+C,D]");
+ cmp = TypeSpecComparer.Create("A", 2)
+ .WithGenericParams(
+ TypeSpecComparer.Create("B").WithNested("C"),
+ TypeSpecComparer.Create("D"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B,C[D]]");
+ cmp = TypeSpecComparer.Create("A", 2)
+ .WithGenericParams(
+ TypeSpecComparer.Create("B"),
+ TypeSpecComparer.Create("C", 1)
+ .WithGenericParams(
+ TypeSpecComparer.Create("D")));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B[C],D[E,F]]");
+ cmp = TypeSpecComparer.Create("A", 2)
+ .WithGenericParams(
+ TypeSpecComparer.Create("B", 1)
+ .WithGenericParams(
+ TypeSpecComparer.Create("C")),
+ TypeSpecComparer.Create("D", 2)
+ .WithGenericParams(
+ TypeSpecComparer.Create("E"),
+ TypeSpecComparer.Create("F")));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[B[C,D[E,F]],G]");
+ cmp = TypeSpecComparer.Create("A", 2)
+ .WithGenericParams(
+ TypeSpecComparer.Create("B", 2)
+ .WithGenericParams(
+ TypeSpecComparer.Create("C"),
+ TypeSpecComparer.Create("D", 2)
+ .WithGenericParams(
+ TypeSpecComparer.Create("E"),
+ TypeSpecComparer.Create("F"))),
+ TypeSpecComparer.Create("G"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B[C,D[E,F]]");
+ cmp = TypeSpecComparer.Create("A")
+ .WithNested(("B", 2))
+ .WithGenericParams(
+ TypeSpecComparer.Create("C"),
+ TypeSpecComparer.Create("D", 2)
+ .WithGenericParams(
+ TypeSpecComparer.Create("E"),
+ TypeSpecComparer.Create("F")));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+
+ spec = ClrTypeSpec.Parse("A[ [B, AssemblyB], C, [D, AssemblyD]], AssemblyA");
+ cmp = TypeSpecComparer.Create("A", 3)
+ .WithAssembly("AssemblyA")
+ .WithGenericParams(
+ TypeSpecComparer.Create("B").WithAssembly("AssemblyB"),
+ TypeSpecComparer.Create("C"),
+ TypeSpecComparer.Create("D").WithAssembly("AssemblyD"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+
+ [Test]
+ public void SimpleNestedGeneric_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A[T]+B]");
+ var cmp = TypeSpecComparer.Create("A", 1)
+ .WithNested("B")
+ .WithGenericParams(
+ TypeSpecComparer.Create("T"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B[T]");
+ cmp = TypeSpecComparer.Create("A")
+ .WithNested(("B", 1))
+ .WithGenericParams(
+ TypeSpecComparer.Create("T"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B[T]+C");
+ cmp = TypeSpecComparer.Create("A")
+ .WithNested(("B", 1), ("C", 0))
+ .WithGenericParams(
+ TypeSpecComparer.Create("T"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A+B+C[T]");
+ cmp = TypeSpecComparer.Create("A")
+ .WithNested(("B", 0), ("C", 1))
+ .WithGenericParams(
+ TypeSpecComparer.Create("T"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+
+ spec = ClrTypeSpec.Parse("A+B[T]+C[U,V]");
+ cmp = TypeSpecComparer.Create("A")
+ .WithNested(("B", 1), ("C", 2))
+ .WithGenericParams(
+ TypeSpecComparer.Create("T"),
+ TypeSpecComparer.Create("U"),
+ TypeSpecComparer.Create("V"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[T]+B+C+D[U,V]+E");
+ cmp = TypeSpecComparer.Create("A", 1)
+ .WithNested(("B", 0), ("C", 0), ("D", 2), ("E", 0))
+ .WithGenericParams(
+ TypeSpecComparer.Create("T"),
+ TypeSpecComparer.Create("U"),
+ TypeSpecComparer.Create("V"));
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+
+ [Test]
+ public void NestedGenericWithModifiers_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A[T]+B+C+D[U,V]+E[]");
+ var cmp = TypeSpecComparer.Create("A", 1)
+ .WithNested(("B", 0), ("C", 0), ("D", 2), ("E", 0))
+ .WithGenericParams(
+ TypeSpecComparer.Create("T"),
+ TypeSpecComparer.Create("U"),
+ TypeSpecComparer.Create("V"))
+ .WithModifiers(new ClrArraySpec(1, false));
+ Assert.That(cmp.SameAs(spec), Is.True);
+
+ spec = ClrTypeSpec.Parse("A[T]+B+C+D[U,V]+E[,]**&");
+ cmp = TypeSpecComparer.Create("A", 1)
+ .WithNested(("B", 0), ("C", 0), ("D", 2), ("E", 0))
+ .WithGenericParams(
+ TypeSpecComparer.Create("T"),
+ TypeSpecComparer.Create("U"),
+ TypeSpecComparer.Create("V"))
+ .WithModifiers(new ClrArraySpec(2, false), new ClrPointerSpec(2))
+ .SetIsByRef();
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void NestedGenericAsGenericArg_ParsesCorrectly()
+ {
+ var spec = ClrTypeSpec.Parse("A[B+C[D]]");
+ var cmp = TypeSpecComparer.Create("A", 1)
+ .WithGenericParams(
+ TypeSpecComparer.Create("B").WithNested(("C", 1))
+ .WithGenericParams(
+ TypeSpecComparer.Create("D")));
+ Assert.That(cmp.SameAs(spec), Is.True);
+ }
+
+ [Test]
+ public void GenericArg_CannotBeByRef()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[B&]"));
+ Assert.That(exn.Message, Does.Contain("Generic argument can't be byref or pointer type"));
+ }
+
+ [Test]
+ public void GenericArg_CannotBePointer()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[B*]"));
+ Assert.That(exn.Message, Does.Contain("Generic argument can't be byref or pointer type"));
+
+ }
+
+ [Test]
+ public void CannotTakeByRefOfByRef()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A&&"));
+ Assert.That(exn.Message, Does.Contain("Can't have a byref of a byref"));
+
+
+
+ }
+
+ [Test]
+ public void CannotHavePointerAfterByRef()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A&*"));
+ Assert.That(exn.Message, Does.Contain("Can't have a pointer to a byref type"));
+ }
+
+ [Test]
+ public void CannotHaveMissingCloseBracketInGenericArgumentAssemblyName()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[[B, AssemblyB"));
+ Assert.That(exn.Message, Does.Contain("Unmatched ']' while parsing generic argument assembly name"));
+ }
+
+ [Test]
+ public void ByRefQualifierMustBeLast()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A&[]"));
+ Assert.That(exn.Message, Does.Contain("Byref qualifier must be the last one of a type"));
+ }
+
+ [Test]
+ public void MissingCharactersAfterLeftBracketIsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A["));
+ Assert.That(exn.Message, Does.Contain("Invalid array/generic spec"));
+ }
+
+ [Test]
+ public void CannotHaveGenericArgsAfterArrayOrPointer()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[][B]"));
+ Assert.That(exn.Message, Does.Contain("generic args after array spec or pointer type"));
+
+ exn = Assert.Throws(() => ClrTypeSpec.Parse("A*[B]"));
+ Assert.That(exn.Message, Does.Contain("generic args after array spec or pointer type"));
+ }
+
+ [Test]
+ public void InvalidGenericArgsSeparator_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[ [B, AssemblyB ] + C ] "));
+ Assert.That(exn.Message, Does.Contain("Invalid generic arguments separator"));
+ }
+
+ [Test]
+ public void ErrorParsingGenericParamsSpec_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[B,"));
+ Assert.That(exn.Message, Does.Contain("Error parsing generic params spec"));
+ }
+
+ [Test]
+ public void TwoBoundDesignatorsInArraySpec_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[**]"));
+ Assert.That(exn.Message, Does.Contain("Array spec cannot have 2 bound dimensions"));
+ }
+
+ [Test]
+ public void InvalidCharacterInArraySpec_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[*!]"));
+ Assert.That(exn.Message, Does.Contain("Invalid character in array spec"));
+ }
+
+ [Test]
+ public void ErrorParsingArraySpec_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[,"));
+ Assert.That(exn.Message, Does.Contain("Error parsing array spec"));
+ }
+
+ [Test]
+ public void CannotHaveBoundAndDimensionTogetherInArraySpec()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[*,]"));
+ Assert.That(exn.Message, Does.Contain("Invalid array spec, multi-dimensional array cannot be bound"));
+ }
+
+
+ [Test]
+ public void UnmatchedRightBracket_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[]]"));
+ Assert.That(exn.Message, Does.Contain("Unmatched ']'"));
+ }
+
+
+ [Test]
+ public void UnknownCharacterInModifiers_IsBad()
+ {
+ var exn = Assert.Throws(() => ClrTypeSpec.Parse("A*!"));
+ Assert.That(exn.Message, Does.Contain("Bad type def"));
+ }
+
+ // I don't know how to trigger this error
+ //[Test]
+ //public void UnclosedAssemblyQualifiedNameInGenericArg_IsBad()
+ //{
+ // var exn = Assert.Throws(() => ClrTypeSpec.Parse("A[ [B, AssemblyB ] ] "));
+ // Assert.That(exn.Message, Does.Contain("Unclosed assembly-qualified type name"));
+ //}
+}
+
diff --git a/Clojure/Csharp.Tests/TypeNameTests2/TypeNameResolvingTests2.cs b/Clojure/Csharp.Tests/TypeNameTests2/TypeNameResolvingTests2.cs
new file mode 100644
index 00000000..63d345f5
--- /dev/null
+++ b/Clojure/Csharp.Tests/TypeNameTests2/TypeNameResolvingTests2.cs
@@ -0,0 +1,162 @@
+namespace Csharp.Tests.TypeNameTests2;
+internal class TypeNameResolvingTests2
+{
+}
+
+
+//using clojure.lang;
+//using NUnit.Framework;
+//using System;
+//using System.Collections.Generic;
+
+//namespace Csharp.Tests;
+
+//public class TypeA { }
+//public class OneG { }
+//public class TwoG { }
+//public class GenParent
+//{
+// public class Child
+// {
+// public class GrandChild
+// {
+// public class GreatGrandChild
+// {
+
+// }
+// }
+// }
+//}
+
+
+//[TestFixture]
+//public class TypeNameParsingTests
+//{
+// static Namespace _ns;
+
+// [OneTimeSetUp]
+// public void Setup()
+// {
+// RT.Init();
+
+// _ns = Namespace.findOrCreate(Symbol.intern("Csharp.Tests"));
+
+// _ns.importClass(Symbol.intern("TTypeA"), typeof(TypeA));
+// _ns.importClass(Symbol.intern("TOneG"), typeof(OneG<>));
+// _ns.importClass(Symbol.intern("TTwoG"), typeof(TwoG<,>));
+// _ns.importClass(Symbol.intern("TGenParent"), typeof(GenParent<,>));
+
+// RT.CurrentNSVar.bindRoot(_ns);
+// }
+
+// [TestCase("System.String", typeof(string))]
+// [TestCase("Csharp.Tests.TypeA", typeof(TypeA))]
+// public void NamespaceQualifiedClassName_ParsesCorrectly(string typename, Type expectedType)
+// {
+// var type = RT.classForName(typename);
+// Assert.That(type, Is.EqualTo(expectedType));
+// }
+
+// [TestCase("int", typeof(int))]
+// [TestCase("long", typeof(long))]
+// [TestCase("float", typeof(float))]
+// [TestCase("double", typeof(double))]
+// [TestCase("bool", typeof(bool))]
+// [TestCase("char", typeof(char))]
+// [TestCase("byte", typeof(byte))]
+// [TestCase("uint", typeof(uint))]
+// [TestCase("ulong", typeof(ulong))]
+// [TestCase("ushort", typeof(ushort))]
+// [TestCase("sbyte", typeof(sbyte))]
+// [TestCase("ints", typeof(int[]))]
+// [TestCase("longs", typeof(long[]))]
+// [TestCase("floats", typeof(float[]))]
+// [TestCase("doubles", typeof(double[]))]
+// [TestCase("bools", typeof(bool[]))]
+// [TestCase("chars", typeof(char[]))]
+// [TestCase("bytes", typeof(byte[]))]
+// [TestCase("uints", typeof(uint[]))]
+// [TestCase("ulongs", typeof(ulong[]))]
+// [TestCase("ushorts", typeof(ushort[]))]
+// [TestCase("sbytes", typeof(sbyte[]))]
+// public void ClojureTypeAlias_ParsesCorrectly(string typename, Type expectedType)
+// {
+// var type = RT.classForName(typename);
+// Assert.That(type, Is.EqualTo(expectedType));
+// }
+
+// [TestCase("TTypeA", typeof(TypeA))]
+// [TestCase("TOneG", typeof(OneG<>))]
+// [TestCase("TTwoG", typeof(TwoG<,>))]
+// [TestCase("TGenParent", typeof(GenParent<,>))]
+// [TestCase("String", typeof(string))]
+// [TestCase("Int32", typeof(int))]
+// public void AliasedTypename_ParsesCorrectly(string typename, Type expectedType)
+// {
+// var type = RT.classForName(typename);
+// Assert.That(type, Is.EqualTo(expectedType));
+// }
+
+// [TestCase("int[]", typeof(int[]))]
+// [TestCase("String[]", typeof(string[]))]
+// [TestCase("TTypeA[]", typeof(TypeA[]))]
+// [TestCase("Csharp.Tests.TypeA[]", typeof(TypeA[]))]
+// public void ArrayType_ParsesCorrectly(string typename, Type expectedType)
+// {
+// var type = RT.classForName(typename);
+// Assert.That(type, Is.EqualTo(expectedType));
+// }
+
+
+// [TestCase("int*", typeof(int))]
+// [TestCase("String*", typeof(String))]
+// [TestCase("TTypeA*", typeof(TypeA))]
+// [TestCase("Csharp.Tests.TypeA*", typeof(TypeA))]
+// public void PointerType_ParsesCorrectly(string typename, Type expectedType)
+// {
+// var type = RT.classForName(typename);
+// Assert.That(type, Is.EqualTo(expectedType.MakePointerType()));
+// }
+
+// [TestCase("int&", typeof(int))]
+// [TestCase("String&", typeof(String))]
+// [TestCase("TTypeA&", typeof(TypeA))]
+// [TestCase("Csharp.Tests.TypeA&", typeof(TypeA))]
+// public void ByRefType_ParsesCorrectly(string typename, Type expectedType)
+// {
+// var type = RT.classForName(typename);
+// Assert.That(type, Is.EqualTo(expectedType.MakeByRefType()));
+// }
+
+// [TestCase("TOneG[int]", typeof(OneG))]
+// [TestCase("TOneG[String]", typeof(OneG))]
+// [TestCase("TTwoG[int,String]", typeof(TwoG))]
+// public void GenericType_ParsesCorrectly(string typename, Type expectedType)
+// {
+// var type = RT.classForName(typename);
+// Assert.That(type, Is.EqualTo(expectedType));
+// }
+
+// [TestCase("System.Collections.Generic.Dictionary`2[System.String, System.Collections.Generic.List`1[System.Int64]]",
+// typeof(Dictionary>))]
+// //[TestCase("Csharp.Tests.TwoG`2[System.Int32, Csharp.Tests.OneG`1[System.String]]", typeof(TwoG>))]
+
+// //[TestCase("TTwoG[int, TOneG[String]]", typeof(TwoG>))]
+// public void GenericType_ParsesCorrectly1(string typename, Type expectedType)
+// {
+// var type = RT.classForName(typename);
+// Assert.That(type, Is.EqualTo(expectedType));
+// }
+
+// [TestCase("System.Collections.Generic.Dictionary`2[System.String, System.Collections.Generic.List`1[System.Int64]]",
+// typeof(Dictionary>))]
+// [TestCase("TTwoG[int, TOneG[String]]", typeof(TwoG>))]
+// public void GenericType_ParsesCorrectly2(string typename, Type expectedType)
+// {
+// var type = ClrTypeSpec.GetTypeFromName(typename);
+// Assert.That(type, Is.EqualTo(expectedType));
+// }
+
+
+//}
+