Resolution

Once the parser has constructed an AST tree for us what we have is then a tree of nodes with nested nodes and nested nodes of nodes, so on… . This is great, but we need to be able to search this tree for certain things we may want to find - perhaps by some predicate such as searching by name. All of this is made possible by the resolver.

Containers, programs and modules

Before we examine the resolver’s API and how to use it it is worth understanding the main important types that play a big role in the resolution process.

The interfaces of importance:

  1. Container
    • Anything which is a Container will have methods allowing one to add Statement(s) to its body and retrieve all of said added Statement(s)
    • It also as of recently implies that methods from the MStatementSearchable and MStatementReplaceable interfaces are available as well - but those won’t be covered here as they are nopt important for the ase functionality

The concrete types of importance:

  1. Program
    • A Program holds multiple Module(s)
    • It is a kind-of Container but NOT any sort of Statement at all
  2. Module
    • It is a kind-of Container and an Entity
  3. Entity
    • You would have seen this earlier, anything which is an entity has a name associated with it

Container API

Let us quickly provide a breakdown of what methods the Container interface type requires one to have implemented and readily available for usage on the implementing type.

Method Return type Description
addStatement(Statement) void Appends the given statement to this container’s body
addStatements(Statement[]) void Appends the list of statements (in order) to this container’s body
getStatements() Statement[] Returns the body of this container

We mentioned that the Container interface also implements the MStatementSearchable and MStatementReplaceable interfaces. Those are important but their applicability is not within the resolution process at all, so they are excluded from the above method listing.

Program API

The program holds a bunch of modules as its body statements (hence being a Container type). A program,, unlike a module, is not an Entity - meaning it has no name associated with it but it is the root of the AST tree.

Method Return type Description
getModules() Module[] Returns the list of all modules which make up this program.
setEntryModule(ModuleEntry, Module) void Given a module entry this will assign (map) a module to it. Along with doing this the incoming module shall be added to the body of this Program and this module will have its parent set to said Program.
markEntryAsVisited(ModuleEntry) void Marks the given entry as present. This effectively means simply adding the name of the incoming module entry as a key to the internal map but without it mapping to a module in particular.
isEntryPresent(ModuleEntry) bool Check if the given module entry is present. This is based on whether a module entry within the internal map is present which has a name equal to the incoming entry.

Some of the methods above are related to the Container-side of the Program type. These methods are useful once the Program is already fully constructed, i.e. all parsing has been completed.

Some of the other methods relating to the markEntryAsVisited(ModuleEntry) and so forth have to do with the mechanism by which the parser adds new modules to the program during parsing and ensures that no cycles are traversed (i.e. when a module is already being visited it should not be visited again).

The resolver

Now that we have a good idea of the types involved we can take a look at the API which the resolver has to offer and how it may be used in order to generate names of entities and perform the resolution of entities.

Let’s first take a look at the constructor that the Resolver has:

this
(
    Program program,
    TypeChecker typeChecker
)

This constructs a new resolver with the given root program and the type checking instance. This implies you must have performed parsing, constructed a TypeChecker and only then could you instantiate a resolver.

Name resolution

Now that we know how to construct a resolver, let’s see what methods it makes available to every component from the TypeChecker (as it is constructed here) and onwards.

The first set of methods relate to the name generation of entities in the AST tree.

Method Return type Description
isDescendant(Container, Entity) bool Returns true entity e is c or is within (contained under c), false otherwise
generateName0(Container, Entity) string[] Generates the components of the path from a given entity up to (and including) the given container. The latter implies that the given Container must also be a kind-of Entity such that a name can be generated from it.
generateNameBest(Entity) string Generate the absolute full path of the given entity without specifying which anchor point to use.
generateName(Container, Entity) string Given an entity and a container this will generate the entity’s full path relative to the given container. If the container is a Program then the absolute name of the entity is derived.

How isDescendant(Container, Entity) works

The first check we do is an obvious one, check if the provided entity is equal to that of the provided container, in that case it is a descendant by the rule.

/**
 * If they are the same
 */
if (c == e)
{
    return true;
}

If this is not the case then we check the ancestral relationship by traversing from the entity upwards.

We start off with this loop variable for our do-while loop:

Entity currentEntity = e;

Steps:

The process of checking for descendance is now described and the actual implementation will follow.

  1. At each iteration we obtain currentEntity’s parent by using parentOf(), we store this as parentOfCurrent
  2. If the parentOfCurrent is equal to the given container then we exit and return true. This is the case whereby the direct parent is found.
  3. If not, then…
    1. Every other case, use current entity’s parent as starting point and keep climbing
    2. If no match is found in the intermediary we will eventually climb to the Program node. Since a Program is a Container but is not** an Entity it will fail to cast and currentEntity will be null, hence exiting the loop and returning with false.
do
{
    gprintln
    (
        format("c isdecsenat: %s", c)
    );
    gprintln
    (
        format("currentEntity: %s", currentEntity)
    );

    Container parentOfCurrent = currentEntity.parentOf();
    gprintln
    (
        format("currentEntity(parent): %s", parentOfCurrent)
    );

    // If the parent of the currentEntity
    // is what we were searching for, then
    // yes, we found it to be a descendant
    // of it
    if(parentOfCurrent == c)
    {
        return true;
    }

    // Every other case, use current entity's parent
    // as starting point and keep climbing
    //
    // This would also be null (and stop the search
    // if we reached the end of the tree in a case
    // where the given container to anchor by is
    // the `Program` BUT was not that of a valid one
    // that actually belonged to the same tree as
    // the starting node. This becomes `null` because
    // remember that a `Program` is not a kind-of `Entity`
    currentEntity = cast(Entity)(parentOfCurrent);
}
while (currentEntity);

return false;

How generateNameBest(Entity) works

The definition of this method is suspiciously simple:

public string generateNameBest(Entity entity)
{
    assert(entity);
    return generateName(this.program, entity);
}

So what’s going on? Well…

What this will do is call generateName(Container, Entity) with the container set to the Program, this will therefore cause the intended behavior described above - see the aforementioned method for the reason as to why this works out.

This will climb the AST tree until it finds the containing Module of the given entity and then it will generate the name using that as the anchor - hence giving you the absolute path (because remember, a Program has no name, next best is the Module).

How generateName(Container, Entity) works

The definition of this method is where the real complexity is housed. This also accounts for how the previous method, generateNameBest(Entity), is implemented.

Firstly we ensure that both arguments are non-null with:

assert(relativeTo);
assert(entity);

A special case is when the container is a Program, in that case the entity’s containing Module will be found and the name will be generated relative to that. Since Program’s have no names, doing such a call gives you the absolute (full path) of the entity within the entire program as the Module is the second highest in the AST tree and first Entity-typed object, meaning first “thing” with a name.

if(cast(Program)relativeTo)
{
    Container potModC = findContainerOfType(Module.classinfo, entity);
    assert(potModC); // Should always be true (unless you butchered the AST)
    Module potMod = cast(Module)potModC;
    assert(potMod); // Should always be true (unless you butchered the AST)

    return generateName(potMod, entity);
}

Given an entity and a container this will generate the entity’s full path relative to the given container. This means calling generateName0(Container, Entity) and then joining each path element with a period.

string[] name = generateName0(relativeTo, entity);
string path;
for (ulong i = 0; i < name.length; i++)
{
    path ~= name[name.length - 1 - i];

    if (i != name.length - 1)
    {
        path ~= ".";
    }
}

return path;

Once path is calculated we then finally return with it.

How generateName0(Container, Entity) works

Let’s first look at how generateName0(Container relativeTo, Entity entity) is implemented. The idea behind this method is to generate an array of strings, i.e. string[], which contains the highest node in the hierachy to the lowest node (then given entity) from left to right respectively.

As mentioned the given container, relativeTo, has to be a kind-of Entity as well such that a name can be generated for it, hence we ensure that the developer is not misusing it with the first check:

Entity containerEntity = cast(Entity) relativeTo;
assert(containerEntity);

Steps:

  1. The first check we then do is to see whether or not the relativeTo == entity
    1. If so, then we simply return a singular path element of containerEntity.getName()
  2. The next check is to check whether or not the given entity is a descendant, either directly or indirectly, of the given container
    1. If so, then we begin generating the elements by swimming up the ancestor tree, stopping once the relativeTo is reached
  3. The last check, if neither checks \(1\) or \(2\) were true, is to return null (an empty array)

The above steps are shown now below in their code form:

/**
 * If the Entity and Container are the same then
 * just returns its name
 */
if (relativeTo == entity)
{
    return [containerEntity.getName()];
}
/**
 * If the Entity is contained within the Container
 */
else if (isDescendant(relativeTo, entity))
{
    string[] items;

    Entity currentEntity = entity;
    do
    {
        items ~= currentEntity.getName();

        /**
         * So far all objects we have being used
         * of which are kind-of Containers are also
         * and ONLY also kind-of Entity's hence the
         * cast should never fail.
         * 
         * This method is never called with,
         * for example, a `Program` relativeTo.
         */
        assert(cast(Entity) currentEntity.parentOf());
        currentEntity = cast(Entity)(currentEntity.parentOf());
    }
    while (currentEntity != relativeTo);

    /* Add the relative to container */
    items ~= containerEntity.getName();

    return items;
}
/** 
 * If not
 */
else
{
    return null;
}

Entity resolution

The second set of methods relate to the resolution facilities made available which allow one to search for entities based on various different sorts of custom predicates and by name.

Method Return type Description
resolveWithin(Container, Predicate!(Entity), ref Entity[]) void Performs a horizontal-level search of the given Container, returning a found Entity when the predicate supplied returns a positive verdict on said entity then we add an entry to the ref parameter
resolveWithin(Container, Predicate!(Entity)) Entity Performs a horizontal-level search of the given Container, returning a found Entity when the predicate supplied returns a positive verdict on said entity then we return it.
resolveUp(Container, Predicate!(Entity)) Entity Performs a horizontal-based search of the given Container, returning the first Entity found when a positive verdict is returned from having the provided predicate applied to it. If the verdict is false then we do not give up immediately but rather recurse up the parental tree searching the container of the current container and applying the same logic.
resolveBest(Container, string) Entity This will do a best effort search starting for an entity with the given name. The search will start from the given container and perform a search within it, in the case no such entity is found there then it will recurse upwards, stopping when you reach the program-level. This also handles special cases such as dotted-paths, it can decode them and follow the trail to the intended entity. In the case that the container given is a Program then each name must either be solely a module name or a dotted-path beginning with one. In this mode nothing else is accepted, it effectively an absolute downwards (rather than potentially upwards search).
findContainerOfType(TypeInfo_Class, Statement) Container Given a type-of Container and a starting Statement (AST node) this will swim upwards to try and find the first matching parent of which is of the given type (exactly, not kind-of).

Only the important methods here will be mentioned. Methods pertaining to certain single-item return and predicate generation will not. For those please go examine the source code; see resolution.d for those codes.

How resolution within works

The method resolveWithin(Container, Predicate!(Entity), ref Entity[] collection) is responsible for providing a facility where by a given predicate can be applied to all entities available at the immediate level of the given container.

With this understanding one can imagine that the implementation if rather simple then:

gprintln
(
    format
    (
        "resolveWithin(cntnr=%s) entered",
        currentContainer
    )
);
Statement[] statements = currentContainer.getStatements();
gprintln
(
    format
    (
        "resolveWithin(cntnr=%s) container has statements %s",
        currentContainer,
        statements
    )
);

foreach(Statement statement; statements)
{
    Entity entity = cast(Entity) statement;

    if(entity)
    {
        if(predicate(entity))
        {
            collection ~= entity;               
        }
    }
}

Simply iterate over all statements present within the container (immediately, not considering nested one) and apply the predicate to each. If a match is found then add it to the collection, otherwise continue iterating.

How resolving upwards works

The method resolveUp(Container currentContainer, Predicate!(Entity) predicate) performs a horizontal-based search of the given Container, returning the first Entity found when a positive verdict is returned from having the provided predicate applied to it. We can see this below:

/* Try to find the Entity within the current Container */
gprintln
(
    format
    (
        "resolveUp(c=%s, pred=%s)",
        currentContainer,
        predicate
    )
);
Entity entity = resolveWithin(currentContainer, predicate);
gprintln
(
    format
    (
        "resolveUp(c=%s, pred=%s) within-search returned '%s'",
        currentContainer,
        predicate,
        entity
    )
);

/* If we found it return it */
if(entity)
{
    return entity;
}

If the verdict is false and the currentContainer is a kind-of Program then it means that there is no further up we can go and we must return null:

else if(cast(Program)currentContainer)
{
    gprintln
    (
        format
        (
            "resolveUp(cntr=%s, pred=%s) Entity was not found and we cannot crawl any further up as we are at the Program container now",
            currentContainer,
            predicate
        )
    );

    return null;
}

However if the verdict is false but the currentContainer is not** a kind-of Program then we do not give up immediately but rather recurse up the parental tree searching the container of the current container and applying the same logic.

/**
 * We will ONLY ever have a `Container`
 * here of which is ALSO an `Entity`.
 */
assert(cast(Entity)currentContainer);
Container possibleParent = (cast(Entity) currentContainer).parentOf();

gprintln
(
    format
    (
        "resolveUp(c=%s, pred=%s) cur container typeid: %s",
        currentContainer,
        predicate,
        currentContainer
    )
);
gprintln
(
    format
    (
        "resolveUp(c=%s, pred=%s) possible parent: %s",
        currentContainer,
        predicate,
        possibleParent
    )
);

/* Can we go up */
if(possibleParent)
{
    return resolveUp(possibleParent, predicate);
}
/* If the current container has no parent container */
else
{
    gprintln
    (
        format
        (
            "resolveUp(c=%s, pred=%s) Simply not found ",
            currentContainer,
            predicate
        )
    );
    return null;
}

How best-effort resolution works

Best effort resolution is now described in this section. The method of concern for this is resolveBest(Container c, string name).

Steps:

  1. We first obtain the path as a string[] by splitting the incoming name by any periods present (.s)
  2. If the container c is a kind-of Program then
    1. If the path is a single element i.Search for a module with the name of path[0]
    2. If the path is more than a single element then we take it that path[0] is the name of a module, we first search for that
      1. If not found we return null
      2. If found we then call resolveBest(moduleFound, join(path[1..$], '.'), so we re-anchor our search based on the module as the container node for the recursive call and the rest of the search path is handed off to the nested call.
  3. If not** and we have a single element in the path then we have a few more checks which follow
    1. We check if any of the modules within the current program matches the name
    2. If no match is found then we try to resolve the name (in other words path[0]) upwards
  4. If the path has more than one element
    1. If path[0] refers to the container entity c then…
      1. If there is only one element left, namely, path[1], then we return with the result of calling resolveWithin(c, path[1]).
      2. If there are more than two elements then what we effectively do is these several steps. First, we check that there is an entity at path[1] by resolving it against c with resolveWithin(c, path[1]); if null we then return null, else we continue and call the found entity entityNext. Then we calculate as such, if the path was x.y.z then we make a newPath containing y.z. We now will resolve the newPath (the y.z) against entityNext (which we cast to a Container and ensure it is possible and call it containerWithin); this is accomplished with resolveBest(containerWithin, newPath). Thus setting in motion the path walking recursive nature of this part of the algorithm.
    2. If path[0] does not refer to the container entity c, then…
      1. First we check if the path[0] matches the name of any module attached to the current program. If a match is found then we return with a call to resolveBest(curModule, name) and let it handle that. We do this so that module names are always treated as absolute and hence can always be referenced, unlike other containers which can have duplicate names if distanced away by at least one non-name-sharing container.
      2. If a module name match is not found then we attempt the following. We try to find an entity named by path[0] by resolving upwards, if we do **not find one, we return null, else if we do then: We will use the found entity as a container called con and then do a resolveBest(con, name) in order to try and find it. This effectively is a step to find the nearest anchoring point (as c clearly isn’t it) and then start the search from there.

The code for this is shown below. Note that it is quite a hefty piece of code but it does after all entail the above process.

gprintln
(
    format
    (
        "resolveBest(cntnr=%s, name=%s) Entered",
        c,
        name
    )
);
string[] path = split(name, '.');
assert(path.length); // We must have _something_ here

// Infact this should probably only be
// ...called relative to a Module, there
// are only some cases where it makes sense
// otherwise
if(cast(Program)c)
{
    gprintln
    (
        format
        (
            "resolveBest: Container is program (%s)",
            c
        )
    );
    Program programC = cast(Program)c;

    // If you were asking just for the module
    // e.g. `simple_module`
    //
    // Note that this won't consider doing
    // a find of the entity in any other module
    // if the path = ['g']. The reason for that is
    // because a search rooted at the `Program`
    // could find such an entity in ANY of the
    // modules if we added such support but that
    // would be kind of useless
    if(path.length == 1)
    {
        string moduleRequested = name;
        foreach(Module curMod; programC.getModules())
        {
            gprintln
            (
                format
                (
                    "resolveBest(moduleHorizontal): %s",
                    curMod
                )
            );
            if(cmp(moduleRequested, curMod.getName()) == 0)
            {
                return curMod;
            }
        }

        gprintln
        (
            "resolveBest(moduleHoritontal) We found nothing and will not go down from Program to any Module[]. You probably did a rooted search on the Program for a bnon-Module entity, didn't ya?",
            DebugType.ERROR
        );
        return null;
    }
    // If you were asking for some entity
    // anchored within a module
    // e.g.`simple_module.x`
    else
    {
        // First ensure a valid module name as anchor
        string moduleRequested = path[0];
        Container anchor;

        foreach(Module curMod; programC.getModules())
        {
            gprintln
            (
                format
                (
                    "resolveBest(moduleHorizontal): %s",
                    curMod
                )
            );
            if(cmp(moduleRequested, curMod.getName()) == 0)
            {
                anchor = curMod;
                break;
            }
        }

        // If we found the module
        // then do an anchored search
        // on the remaining path
        if(anchor)
        {
            string remainingPath = join(path[1..$], ".");
            return resolveBest(anchor, remainingPath);
        }
        // If we could not find the module
        else
        {
            gprintln
            (
                format
                (
                    "resolveBest(Program root): Could not find module '%s' for ANCHORED access",
                    moduleRequested
                ),
                DebugType.ERROR
            );
            return null;
        }
    }
}

/**
 * All objects that implement Container so far
 * are also Entities (hence they have a name).
 *
 * The above is ONLY true except when you
 * have a `Program` BUT we handle the case
 * whereby `c` is a `Program` above, hence
 * meaning that this code is unreachable in
 * such a case and therefore safe.
 */
Entity containerEntity = cast(Entity) c;
assert(containerEntity);
gprintln
(
    format
    (
        "resolveBest(cntr=%s,name=%s) path = %s",
        c,
        name,
        path
    )
);

/**
 * If no dot
 *
 * Try and find `name` within c
 */
if (path.length == 1)
{
    /**
     * Check if the name, regardless of container,
     * matches any of the roots (modules attached
     * to this program)
     */
    foreach(Module curModule; this.program.getModules())
    {
        if(cmp(name, curModule.getName()) == 0)
        {
            return curModule;
        }
    }

    Entity entityWithin = resolveUp(c, name);

    /* If `name` was in container `c` or above it */
    if (entityWithin)
    {
        return entityWithin;
    }
    /* If `name` was NOT found within container `c` or above it */
    else
    {
        return null;
    }
}
else
{
    /* If the root is the current container */
    if (cmp(path[0], containerEntity.getName()) == 0)
    {
        /* If only 1 left then just grab it */
        if (path.length == 2)
        {
            Entity entityNext = resolveWithin(c, path[1]);
            return entityNext;
        }
        /* Go deeper */
        else
        {
            string newPath = name[indexOf(name, '.') + 1 .. name.length];
            Entity entityNext = resolveWithin(c, path[1]);

            /* If null then not found */
            if (entityNext)
            {
                Container containerWithin = cast(Container) entityNext;

                if (entityNext)
                {
                    return resolveBest(containerWithin, newPath);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }
    }
    /* We need to search higher */
    else
    {
        /**
         * Check if the name is of one of the modules
         * attached to the program
         */
        foreach(Module curModule; this.program.getModules())
        {
            if(cmp(curModule.getName(), path[0]) == 0)
            {
                gprintln
                (
                    format
                    (
                        "About to search for name='%s' in module %s",
                        name,
                        curModule
                    )
                );
                return resolveBest(curModule, name);
            }
        }

        Entity entityFound = resolveUp(c, path[0]);

        if (entityFound)
        {
            Container con = cast(Container) entityFound;

            if (con)
            {
                gprintln("fooook");
                return resolveBest(con, name);
            }
            else
            {
                gprintln("also a kill me");
                return null;
            }
        }
        else
        {
            gprintln("killl me");
            return null;
        }
    }
}

How finding a container of a concrete type works

It is sometimes of use to be able to find a container of a given type. This is something the other methods do not really consider, for them the container anchoring point and the name are well known. There are however cases whereby one may one want to find a container of a certain type given a starting statement - this is what this method provides.

Taking a look at the method definition below:

Container findContainerOfType
(
    TypeInfo_Class containerType,
    Statement startingNode
)

Steps:

  1. If the startingNode is null then we return with null
  2. If the typeid(startingNode), that is the actual type of startingNode, is equal to that of the containerType then we return the startingNode casted to a Container. This is a match on first-call with no swimming upwards.
  3. Else we find the parent of the startingNode and recurse to this method using findContainerOfType(containerType, cast(Container)startingNode.parentOf()). This is a case of us finding the starting node’s parent, and then re-applying the logic, hence swimming up in hopes we find the match somewhere above.

This is a relatively simple algorithm and the implementation is shown below:

gprintln
(
    format
    (
        "findContainerOfType(TypeInfo_Class, Statement): StmtStart: %s",
        startingNode
    )
);
gprintln
(
    format
    (
        "findContainerOfType(TypeInfo_Class, Statement): StmtStart (type): %s",
        startingNode.classinfo
    )
);

// If the given AST object is null, return null
if(startingNode is null)
{
    return null;
}
// If the given AST object's type is of the type given
else if(typeid(startingNode) == containerType)
{
    // Sanity check: You should not be calling
    // with a TypeInfo_Class referring to a non-`Container`
    assert(cast(Container)startingNode);
    return cast(Container)startingNode;
}
// If not, swim up to the parent
else
{
    gprintln
    (
        format
        (
            "parent of %s is %s",
            startingNode,
            startingNode.parentOf()
        )
    );
    return findContainerOfType(containerType, cast(Statement)startingNode.parentOf());
}

Worked examples

Given a program with a single module resolution_test_1 as follows:

string sourceCode = `
module resolution_test_1;

int g;
`

We then setup such a relationship (for the sake of the test):

File dummyFile;
Compiler compiler = new Compiler(sourceCode, "legitidk.t", dummyFile);
compiler.doLex();
compiler.doParse();

Program program = compiler.getProgram();

// There is only a single module in this program
Module modulle = program.getModules()[0];

/* Module name must be resolution_test_1 */
assert(cmp(modulle.getName(), "resolution_test_1")==0);
TypeChecker tc = new TypeChecker(compiler);

We first try and search for an entity named g using the program as the anchoring container:

// Now try to find the variable `d` by starting at the program-level
// this SHOULD fail as it should NOT be allowed
Entity var = tc.getResolver().resolveBest(program, "g");
assert(var is null);

This would fail because any search anchored at the program-level will only be able to resolve names of the form <moduleName>.<entity..., hence the assert(var is null).

After this we then try to find the variable d by starting at the module-level:

// Try to find the variable `d` by starting at the module-level
var = tc.getResolver().resolveBest(modulle, "g");
assert(var);
assert(cast(Variable)var); // Ensure it is a variable

This passes, compared to the last, because the search is anchored at a non-program container and there is an entity named "g" within the module modulle.

After this we should be able to do a rooted search for a module, however, at the Program level for a module name:

Entity myModule = tc.getResolver().resolveBest(program, "resolution_test_1");
assert(myModule);
assert(cast(Module)myModule); // Ensure it is a Module

This passes because, as stated earlier, only module names and (dotted-paths starting with them) are allowed when using resolveBest with a program anchor container.

We then do some tests with descendancy:

// The `g` should be a descendant of the module and the module of the program
assert(tc.getResolver().isDescendant(cast(Container)myModule, var));
assert(tc.getResolver().isDescendant(cast(Container)program, myModule));

We can also do a full path resolution including a dotterd-path, as we alluded to earlier. In this case we resolve using the program as the anchoring container and request resolution for the name "resolution_test_1.g":

// Lookup `resolution_test_1.g` but anchored from the `Program`
Entity varAgain = tc.getResolver().resolveBest(program, "resolution_test_1.g");
assert(varAgain);
assert(cast(Variable)varAgain); // Ensure it is a Variable

The last few are just related to doing name generation, similarly though, with differing anchoring points and methods:

// Generate the name from the program as the anchor
string nameFromProgram = tc.getResolver().generateName(program, var);
gprintln(format("nameFromProgram: %s", nameFromProgram));
assert(nameFromProgram == "resolution_test_1.g");

// Generate the name from the module as the anchor (should be same as above)
string nameFromModule = tc.getResolver().generateName(cast(Container)myModule, var);
gprintln(format("nameFromModule: %s", nameFromModule));
assert(nameFromModule == "resolution_test_1.g");

// Generate absolute path of the entity WITHOUT an anchor point
string bestName = tc.getResolver().generateNameBest(var);
gprintln(format("bestName: %s", bestName));
assert(bestName == "resolution_test_1.g");