dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19k stars 4.03k forks source link

[C# Feature Request] Hierarchy Data Type #16193

Closed RandyBuchholz closed 6 years ago

RandyBuchholz commented 7 years ago

Background

There are a few threads that talk about things like nested enums, and other need to represent hierarchal structures. To define a unique spot in a hierarchy we use things like nested namespaces, nested classes, or specialized parser classes. There is no built-in way. On occasion, just to be able to locate a typed-hierarchy object, I've even resorted to things like

namespace ServiceCatalog {
    namespace Member {
        namespace Membership {
            public abstract partial class Join {
                public enum Apply { SaveApplication, SubmitApplication }
                public enum Review { EvaluateApplication, SendNotifycations }
            }
        }
    }
}

so I can happily dot my way around magic strings and keep things unique in a hierarchy. (Please don't judge me...)

There have been discussions related to this like posts Proposal: Record Enum Types #6739 Proposal: Nested enums #14720 Feature Request: enum to act like types #3704

But I think it may make more sense to create a new type instead of modifying existing types.

Proposal

Create a new datatype "Hierarchy" that represents a hierarchy of Type. Conceptually

Enumerable<Type>{
    Enumerable<Type>{
        Enumerable<Type>{}
    }
}

This type could be used in whole or part, to locate types in a hierarchy, or even serve as a meta-type.

It could be used alone, like a nested "enum":

Hierarchy Level0.Level1.Level2.Value = "Green";

Hierarchy Level0.Level1.Level2.Values[] = { Green, Red, Yellow }

Hierarchy PrimaryColors { Reds, Blues }
    .Reds { Pink, Blood }
    .Blues { Sky, Navy }

It could also be typed. Each level could represent a different type, but they may need to be restricted to primitives initially.
Just a few different syntax ideas-

Hierarchy<string> Menu { "File", "Edit", "Contact Us" }
    .[File] { "Save", "Close" }
    .[Contact Us] { "Phone", "Email" }

// or (convoluted) example of meta-type definition. This could def partial classes, and even inherit.
Hierarchy Shapes<class> { Rectangle, Ellipse }
    .Rectangle<class> { Square : Rectangle , Rectangle }
        .Square<string> { "Big Square", "Little Square" } 
    .Ellipse<class> { Circle, Ellipse }

Type squareType = typeof(Shapes.Rectangle.Square); // = Class
Type squareType = typeof(Shapes.Rectangle.Square.[Little Square]); // = string (don't like this syntax though)

They should be usable in most places you can use any other basic type. 1) Can declare inside local function 2) Should have shorthand syntax (see next code) 3) Support basic "stringish" functionality

// Shorthand creation (see Item 6 - they are partials, so this doesn't redefine)
Hierarchy TwoQuickNodes.UpperLevel.LowerLevel.LowestLevel
Hierarchy TwoQuickNodes.UpperLevel.AnotherLowerLevel

// Shorthand with "..." and stringformat
var shapesDir = Shapes...Square.ToString("/");
// gives "Shapes/Rectangular/Square" 

// Or sub section
var shapesDir = Shapes[Rectangle...Square];
// produces "Rectangle.Square" 

4) Can use in attribute parameters. You can no longer use enums in attribute parameters. If a Hierarchy is a primitive, you should be able. (See next code)

5) Understand uniqueness. If an element is unique in the hierarchy, it can be addressed directly

// Working similar to an enum, with underlyingType an int
Hierarchy<int> ServiceRoutes { Provider1, Provider2, Provider3 }
    .Provider1 { Interface1, Interface2 }
         .Interface1 { Operation1, Operation2 }
    .Provider2 { Interface1, UniqueName }

[Route(ServiceRoutes[UniqueName]-String('/')] // .ToString() can't be used on attribute params, so something else.
 ActionResult SomeAction(){...}

// produces => "ServiceRoutes/Provider1/UniqueName"

6) Extensible and inheritable. We should be able to derive Hierarchies from Hierarchies. We should also be able to extend them (at design time) from multiple locations. They are inherently partial.

// Using ServiceRoutes from the previous code
Hierarchy<int> ServiceRoutes.Provider1.Interface1 { AddedOperation }

// Understanding uniqueness also works for creation shorthand.
// This adds a two levels below UniqueName
Hierarchy ServiceRoutes[UniqueName] { Child1.Child1dot1 }

I think there is a need to be able to represent hierarchies as true hierarchies, and not have to manually construct nested objects. I don't know what that should actually look like, but I thought I would put out a few ideas.

gafter commented 7 years ago

See also #14208, #8729

gafter commented 7 years ago

Also #9120 #6739

RandyBuchholz commented 7 years ago

It seems there are needs both from a structural and design ease (intellisense) for hierarchies. I did see a couple of those items, and it seemed that somewhere in the root of the problem was hierarchies. My thought is that by creating a low level construct we may be able to create a more generalized solution to hierarchy related issues/needs. In some of the comments it kind-of seemed that the root need was a hierarchy, and the proposal was (partially) a way to solve that using current technology - some through unions and some through nestable enums. I think either or both are nice, but a more generalized and independent solution would be better.

One of the advantages (and drawbacks) with a new type we get new options (and problems). The case that comes to mind is with an nested-enum approach. Two things I have seen requested are nestable enums, and the ability to declare enums in functions. (With local functions I think there is a stronger case to enable that now). My particular problem with enums is not being able to use them in attributes. Changing enums or the way enums are handled to address these items has many potential side effects. With a new type we as not as constrained by the as-is, though of course the work is much greater. (And it's been a while since we've had one :).

In the short term, I think nestable enums would be helpful, though it seems it's not an easy thing to do. I can get around attribute thing pretty easily by passing them as just object, so for my own needs that approach would help. I would like to see a true hierarchal type in 7+1 or +2 though...

As for declaring enums in functions, are you aware of an open item for that? I can't find one. With local functions, declaring them at the class level starts removing them pretty far from point of use in things like switches in a local function. If you are not aware of an existing request I'll open one.

paulomorgado commented 7 years ago

@RandyBuchholz, one nitpicking comment: there are no nested namespaces; only nested namespace definitions.

namespace ServiceCatalog
{
    namespace Member
    {
        namespace Membership
        {
            public abstract partial class Join
            {
                public enum Apply { SaveApplication, SubmitApplication }
                public enum Review { EvaluateApplication, SendNotifycations }
            }
        }
    }
}

is exactly the same as:

namespace ServiceCatalog.Member.Membership
{
    public abstract partial class Join
    {
            public enum Apply { SaveApplication, SubmitApplication }
            public enum Review { EvaluateApplication, SendNotifycations }
    }
}

Help me out here understanding this.

in Shapes.Rectangle.Square, what's the type of Shapes.Rectangle.Square? And Shapes.Rectangle? And Shapes?

Is Shapes.Rectangle.Square a Shapes, for example?

RandyBuchholz commented 7 years ago

@paulomorgado

Shapes

shapes: I tagged that as convoluted because it's getting out there a little, and more discussion thoughts then solid approach. But, the Type would be the type of the last node in the sequence. I'll repeat the block

Hierarchy Shapes<class> { Rectangle, Ellipse }
    .Rectangle<class> { Square : Rectangle , Rectangle }
        .Square<string> { "Big Square", "Little Square" } 
    .Ellipse<class> { Circle, Ellipse }

The first line Hierarchy Shapes<class> { Rectangle, Ellipse } would declare a hierarchy "Shapes", and two classes (<class>) - Rectangle and Ellipse. This is vaguely equivalent to

public partial class Shapes{
    public partial class Rectangle{ }
    public partial class Ellipse{ }
}

A way to read this would be, "Hierarchy Shapes is a set of Classes (Shapes<class>), with members class Rectangle and class Ellipse".

The next line .Rectangle<class> { Square : Rectangle , Rectangle } elaborates the partial Rectangle, to give it child (class) nodes Square and Rectangle. This Rectangle is a different one, because it is at a different level in the hierarchy. This can be confusing initially because in Rectangle<class> it is saying the children of rectangle are classes, not Rectangle itself. The new equivalent is

public partial class Shapes{
    public partial class Rectangle {
        public partial class Square { } 
        public partial class Rectangle { }
    }
}

The Square : Rectangle was meant to show that you can use inheritance, but I think that is a bad example.

So, for Shapes.Rectangle.Square the type would be the (last item) class Square, and Shapes.Rectangle is class Rectangle.

One option is to require all names to be unique in the hierarchy, which I actually prefer initially. Without that, Shapes.Rectangle.Rectangle would be type class Rectangle.Rectangle. That reeks of implementation.

Other

The { } does two things, so it may not be the best syntax. It is both declaring and initializing. I had it declaring so when you have .xxx below you know that you are elaborating a member defined above. I see that may be murky.

Open and Closed

You could do "open" (<type> defaulting to <hierarchy>) and "closed" (<type> declared) quick declarations. If <type> isn't declared the type is <hierarchy>.

Open - type is hierarchy.

Hierarchy Shapes { Rectangle { Square, Rectangle }, Ellipse { Circle, Ellipse } } }

Closed - type is class. (This shows another place you could declare an underlying type - on Hierarchy)

Hierarchy<class>  Shapes { Rectangle { Square, Rectangle }, Ellipse { Circle, Ellipse } } }

I'm up in the air about mixing types in hierarchies general because of implementation difficulties and confusing the base purpose. Starting off with single-type closed hierarchies would likely be the most practical. I can see something like this in the short term

Hierarchy<Enum> Shapes {
    Rectangle {
        Square = { BigSquare, LittleSquare }
        Rectangle = { Regular, Oblique }
    }
    Ellipse {
        Circle = { Hollow, Filled }
        Ellipse = { Round, Oval }
    }
}

Types would likely be restricted to built-in types. A question is are branches also values? Or should they be declared specifically.

Hierarchy<Enum> Shapes {
    Rectangle {
        Square = { BigSquare, LittleSquare }
        Rectangle = { Regular, Oblique }
    }  = { Square, Rectangle }
}

Namespace

namespace: Nitpicking is good. Yes, this is just a fragment. I'll explain a little.

I'm exploring a concept of an MVC framework that exposes actions as "Business Processes". Views are processes, and @models are messages (@model Message). I'm using attribute routing. In the route (IRouteTemplateProvider - [Route(Template=, Name=, Order=)], Template is a "physical address". It is used by the engine for route matching (e.g., controller/action). I'm using Name to represent a "logical address". In this case, a Business Service. The service hierarchy is different than the implementation hierarchy. I need to "publish" that, so routes can be used by their process name (logical) and not physical (route template) at design time. Ideally, I would have a hierarchy type (hierarchy of types declared at design time) to help me.

My work around while I play with this is to reserve a namespace "ServiceCatalog", and place my logical routes in there, as types - enums in this case. So, back to why it looks like that. The Catalog has many nodes and branches. By using that format it is easy to see the overall structure. I'm just exploring this concept (Process Controllers) and I don't know yet if it is viable, so I just threw together a cataloging approach. It feels wrong, but I'm just experimenting. In the root of my application I have ServiceCatalog.cs file to "publish" the services.

namespace ServiceCatalog {
    namespace Member {
        namespace...
             namespace...
    namespace Activity {
        namespace...
        namespace...
    namespace Club {

Each process controller adds its own services using the normal format (or a hybrid). Its just another workaround while I experiment, but the .cs file has a block for the ServiceCatalog before the namespace for the controller

 namespace ServiceCatalog.Club.Activities {
    public abstract class SocialEvents { // This is the controller in the .cs
       public enum ScheduleActivity { // This maps to an action, and the enum values are flow "conditions"

// and then the "real" namespace

namespace AppName.Domain.Club. {
   public class SocialEvents : ProcessController { 

The ugliness of this is what started me down this path. Any suggestions on better approaches for managing design time hierarchies would be appreciated.

paulomorgado commented 7 years ago

In the end, all this has to come down to IL. How do you envision the end result?

RandyBuchholz commented 7 years ago

I don't know IL internals well enough to give an informed answer.

I think at a very low level, a hierarchy is a recursive collection of object. I can get that pretty directly in C# with something like

public class HierarchyNode : Collection<object> {
    public Collection<HierarchyNode> Branchs;
}

I can use it like

public class HierarchyNode : Collection<object> {
    public Collection<HierarchyNode> Branchs = new List<HierarchyNode>(); // newed for test
}

public class HTest{
    public void HTestMethod() {
        var hierarchy = new HierarchyNode();
        hierarchy.Branchs.Add(new HierarchyNode());
        hierarchy.Branchs[0].Add(new HierarchyNode());
    }
}

So it seems the IL supports the concept. It would be up to the plumbing to translate Hierarchy<Enum> into the Hierarchy<object> at runtime.

The language might be able to see Hierarchy<Enum> Shapes { Rectangle { Square, Rectangle }, Ellipse { Circle, Ellipse } } } internally as a set of enums

// Shapes.Rectangle.Square =

enum Shapes { }
enum Shapes_Rectangle { }
enum Shapes_Rectangle_Square { }

I'm getting out of my depths, but those ideas come to mind.

sirgru commented 7 years ago

I think this is a neat idea, there are some cases where we want to reason about the hierarchy, and this could circumvent the need for nested enums / enums inheritance (which is actually more useful than former, as I've realized).

I personally find the syntax above problematic for 2 reasons: 1) Declarations are duplicated, and we want less code duplication, not more. 2) Like in composite builder pattern, when things are appended with '.' there needs to be something that makes a distinction if we're adding to sibling, parent or as child. It seems to me that this distinction could complicate this syntax.

Hierarchy<int> ServiceRoutes { Provider1, Provider2, Provider3 }
    .Provider1 { Interface1, Interface2 }
         .Interface1 { Provider1, Provider2 }   // Legal as ServiceRoutes.Provider1.Interface1.Provider1
    .Provider2 { Interface1, UniqueName }   // I want to define the provider 2 from the top!

So, IMO, the syntax

hierarchy Shapes { 
    Rectangle { 
        Square, 
        Rectangle 
    }, 
    Ellipse { 
        Circle, 
        Ellipse 
    } 
}

is much more natural. It looks like a collection initializer and reveals the structure in proper pieces, like a tree (we may want to collapse parts of the hierarchy in VS, which comes naturaly in this syntax).

I think that using the Hierarchy for something that represents constant data is not optimal, we can already use nested classes for that:

    Hierarchy<string> Menu { "File", "Edit", "Contact Us" }
    .[File] { "Save", "Close" }
    .[Contact Us] { "Phone", "Email" }

we can already do as:

class Program {
    static void Main(string[] args) {
        Console.WriteLine(Menu.File.value);
        Console.WriteLine(Menu.File.Save.value);
        Console.WriteLine(Menu.ContactUs.Phone.value);

        Console.ReadKey();
    }
}

public class Menu {

    public static string value { get {return "Menu"; } }

    public class File {
        public static string value { get {return "File"; } }

        public class Save {
            public static string value { get {return "Save"; } }
        }

        public class Close {
            public static string value { get {return "Close"; } }
        }
    }

    public class Edit {
        public static string value { get {return "Edit"; } }
    }

    public class ContactUs {
        public static string value { get {return "Contact Us"; } }

        public class Phone {
            public static string value { get {return "Phone"; } }
        }

        public class Email {
            public static string value { get {return "E-mail"; } }
        }
    }

}

It's not pretty but we can do it.

So, while IMO that would not be the main use case, there is something here that I think can be very significant. What I think Hierarchies are best for would be "custom metadata for custom types". It sounds strange, but even in the current proposition the use of hierarchy is to denote some type of metadata about the types. For example,

Hierarchy Shapes { Rectangle { Square, Rectangle }, Ellipse { Circle, Ellipse } } }

all this does is allows us to "dot-to" the proper type and gives us some type of "metadata" that Square is a type of Rectangle, which is a type of Shape. Rectangle and Shape don't do anything in the context of the hierarchy except giving this metadata, they have no data and no methods. However, they could, but separate from the hierarchy data type like I will list below.

There could be a shorthand, for example: Hierarchy Level0.Level1.Level2 could in fact be shorthand for hierarchy Level0 {Level1 {Level2 }}. Partial shorthand could also be handy like this:

hierarchy Level0.Level1.Level2 { Green, Red, Yellow } // Slightly modified the OP's syntax here

would in fact be the same as hierarchy Level0 {Level1 {Level2 { Green, Red, Yellow }}}

Also, the quick traversal like Shapes..Square can be pretty neat, but if there are multiple elements with the same name there could be some lack of clarity on the user side (the compiler could just pick the first relevant item in the hierarchy). I think the .. notation would be more appropriate than ..., because it is already intuitive for a "range".


I don't mean to hijack this proposal or anything, but the conceptual fit for hierarchies I have in mind is somewhat different.

The purpose of Hierarchies could be only to denote the hierarchical relationship between types. Such types would either be custom or "empty" (placeholder) types.

Let me expand on that a bit.

For each node in the hierarchy it can be (1) "type constant" similar to an enum value, something that is comparable, printable etc. but does not correspond to a proper type any more than an enum value does. Or, it can be (2) type declaration (but not definition), which provides in this context a relation of this type to other nodes in the hierarchy. So, declaration of something in the hierarchy would be like a partial class, but only in cases where there is actual definition of the class, which would be determined by the compiler. So, for example:

public hierarchy Menu {
    File {
        [PrintOption.FullPath("/")] Save, 
        Close
    },
    Edit,
    ContactUs {
        Phone, 
        [StringValue("E-Mail")] Email
    }
}

public class Menu.File.Save {   // We need to know that this type is a part of hierarchy, so it must be "fully qualified"
    public static void Execute() {
        // Does the operation
    }
}

So, in the example above everything except Menu.File.Save is navigable but has no operations. Nodes could be decorated with attributes. [StringValue("")] would denote what the value of the type is when ToString() is called on it without the need to define the separate class and override the ToString() method. [PrintOption.FullPath("/")] would allow us to get a full path to a hierarchy node separated by "/", so Menu.File.Save.ToString() would print "Menu/File/Save".

Side note: if generic attributes where allowed, we could have something like this: [Value<int>(32)] Item which would allow us to give a constant value and a type to Item.value without any further declaration. From what I've read, they are possible but not allowed. In the absence of anything better, we could have a limited range of allowed attributes like [Intvalue(32)] [FloatValue(32.0f)] etc.

The benefits of it is: a) Easy navigation with "."

b) No need for nested enums. Hierarchy is a superset of functionality of a hypothetical nested enum.

c) Could give proper type information: Shapes..Square is Shapes.Rectangle // true and presumably quickly because the "inheritance" is defined in the hierarchy (no reflection). So, 'is' operation would check up the hierarchy, while exact type can be checked with equality like with (e) below.

d) Less need for enum inheritance. Hierarchy without classes would be a better conceptual fit in some cases. For this to be useful we would need to have hierarchy merging, like so:

public partial hierarchy Menu {
    File { Save }                       // In assembly 1, I implement my Save
}

public partial hierarchy Menu {
    File { Close }                      // In assembly 2, e.g. Framework implements Close();
}

I don't believe inheritance is possible in this context. Instead, it's a "partial hierarchy". Because of that, all nodes must be treated as leaves, since they may be extended elsewhere.

e) Could allow for all members to inherit from the same class:

public interface IExecutable {
    void Execute;
}

public partial hierarchy Menu : IExecutable {
    [DefaultImplementation] 
    File { 
        Close 
    }                       
}

This could mean: 1) All nodes implement IExecutable. 2) If the node is decorated with [DefaultImplementation] attribute, then the explicit definition of this class with proper interfaces implementation is not required. void methods do nothing and methods that would return T return default(T). 3) If not, explicit implementation is required for each node, as with normal inheritance. 4) Implementation classes may add other implementations, but must obey the existing implementations defined in the hierarchy. 5) To denote the class must have public, parameterless c-tor, something like Menu : new() could be used, which is not legal now. Side note: it would be even better if we could have new(T) as a constraint in C#.

e) Could allow for members of hierarchy type, putting into work the examples from above:

public Menu menu = Menu.File.Save;  // public field of Menu type, it is a value type (just like an enum).
....
RunMenuItem(menu);
....
public RunMenuItem(IExecutable item) {
    item.Execute();                 // Legal
}

f) When dealing, for example, with abstract factory, like here (https://hfpatternsincsharp.codeplex.com/SourceControl/latest#AbstractFactory/PizzaStore.cs) we have to instantiate based on type, and this requires switch/case on the type representation to instantiate the appropriate type. This kind of work is extension-unfriendly and error prone. We could instead be supplying a hierarchy and instantiating based on hierarchy variable. That code would then look something like:

// Before
class CheesePizza : Pizza {
    IIngredientFactory ingredientFactory;

    public CheesePizza(IIngredientFactory customIngredientFactory) {
        this.ingredientFactory = customIngredientFactory;
    }

    public override void Prepare() {
        Console.WriteLine("Making custom cheese pizza.");
        this.ingredientFactory.CreateDough();
        this.ingredientFactory.CreateSauce();
        this.ingredientFactory.CreateCheese();
    }
}

public class PortlandPizzaStore : PizzaStore {
    // portland-style preparation for all pizzas
    protected override Pizza CreatePizza(string type) {
        Pizza pizza = null;
        IIngredientFactory ingredientFactory =
            new PortlandIngredientFactory();

        if(type == "Cheese")
            pizza = new CheesePizza(ingredientFactory);
        else if(type == "Clam")
            pizza = new ClamPizza(ingredientFactory);
        else if(type == "Suede")
            pizza = new ShoePizza(ingredientFactory);

        return pizza;
    }
}

// After
class PizzaTypeBase : Pizza {
    IIngredientFactory ingredientFactory;

    public PizzaTypeBase(IIngredientFactory customIngredientFactory) {
        this.ingredientFactory = customIngredientFactory;
    }

    public override void Prepare() {
        this.ingredientFactory.CreateDough();
        this.ingredientFactory.CreateSauce();
        this.ingredientFactory.CreateCheese();
    }
}

public hierarchy PizzaType : PizzaTypeBase, new(IIngredientFactory customIngredientFactory) {
    Cheese, Clam, Shoe
}

public class PizzaType.Clam { // No need to repeat the inheritance, compiler knows and we know by "fully qualified syntax". 
    public Clam(IIngredientFactory cif) : base (cif) {} // Don't have to fully qualify in C-tor.

    public override void Prepare() {
        Console.WriteLine("Making custom cheese pizza.");
        base.Prepare();
    }
}
// Same for Cheese, Shoe, or for any new type we want to add.

public class PortlandPizzaStore : PizzaStore {
    // portland-style preparation for all pizzas
    protected override Pizza CreatePizza(PizzaType pizzaType) {

        IIngredientFactory ingredientFactory = new PortlandIngredientFactory();

        return new pizzaType(ingredientFactory);        // We can now new-up a type based on a variable without reflection!
    }
}

g) Hierarchy can contain different types without any specification or clarification.

RandyBuchholz commented 7 years ago

@sirgru, great comments, thanks. I just came up with that syntax to help illustrate some of the concepts and start a discussion. I have no really investment in the actual syntax.

I don't think our views are actually different. When you started describing your view you said,

"The purpose of Hierarchies could be only to denote the hierarchical relationship between types. Such types would either be custom or "empty" (placeholder) types."

In my view, I abstract that a step further - "The purpose of Hierarchy is only to denote a hierarchical construct." Types - be they nodes types or relation types, are extensions of the basic construct. (I know this is contrary to my original post, which was use-focused.)

I think at its core, a hierarchy type is a "locator", and essentially "type-less" (just type Hierarchy). The first purpose is to be able to place something at a location in a hierarchy, provide methods to "know my location", and to be able to navigate to other locations in the hierarchy.

Next is the ability to perform logical-type operations (union, exclude, XOR, etc.) on these to combine and extract portions of hierarchies. Treating these as locators removes any implied relationships between nodes. (e.g., type, inheritance).

With this in place, the concept is extended to give meaning to the nodes. First, I can see having an underlying/internal type. This would help the engine manipulate the nodes. Following the approach used by Enum (numerical) would make sense for a couple of reasons. One being using hierarchy to implement nested enums. Another would be (looking at the larger MS ecosystem) enabling stronger support for the SQL Server HierarchyID datatype. This type converts to a representation that looks like /numerical/numerical/numerical to represent the location of the node. With a common underlying type, Entity Framework would be (better) able to support the HierarchyID datatype. Instead of having to use the SQL Types namespace and SqlHierarchyId type, this would be native.

From there, user typing/type composition and type relationships (e.g., inheritance) could be added. First is basic node typing or "direct typing". For consistency, I think <> works best here Hierarchy<Type>. A side effect of direct typing a that a node becomes dual-typed. It retains its underlying type, and additionally, has its surface type. Implementing surface types has a few approaches, but basically the two options are a built-in approach where there are actually two types on the base item (I don't know what that would look like internally/IL. The IL does support multiple inheritance, so maybe something along those lines), or through an inheritance approach at the language level - either type inheritance or composition. I think type inheritance is problematic because C# only supports single inheritance, possibly limiting abilities. Composition has its own overhead of having to carry the type container around. It's at this point when your "hierarchical relationship between types" and my original proposal would be realized.

It's at this point in design where questions about things like inheritance need to be resolved. Like, does an untyped node inherit its parent type by default? But, to me these are secondary to having a "type-less hierarchical locator".

I think much of the syntax would naturally fall out in the implementation constraints. Based on this "natural" syntax, candy syntax would be added.

gafter commented 6 years ago

I can't say I really understand this proposal. If you want to continue discussing it, please open a new thread on the csharplang repo.