Repository: rokucommunity/brighterscript
Pull request #1123 (2024-03-26 20:07:39)

Validate single scope
With version 1, we will be changing how validation works. The goal is that all files will still be totally validated, except the method will be different.
Currently, in v0.65, when a file changes, all files in all affected scopes are validated. This validation is rather basic - checking number of function arguments, verifying no duplicate function names, etc.

In version 1, the validation is much more rigorous, as all known types, namespaces, variables, etc. are fully checked for how they are used (as function args, return values, property access, etc.). This is taking MUCH longer to do, especially for files that are included in many scopes, like a utils file with common functions that’s included in every component.
So we are going to experiment with only checking the first scope a file is in, then also producing validation errors if we expect there might be errors in other scopes.

For example, let’s say `FileA` references function `someFunc` that is not defined in the same file, and `FileA` is included in 2 different components.

If `FileA` changes, it will only be fully validated in the scope of the first component. In that context, if `someFunc` is defined (and its usage is consistent with its definition) there will be no error. However, we also check that someFunc is available (and compatible) in the context of the second scope.

If `someFunc` is not defined in the second component, or it has a different function signature, we will add a diagnostic error to say that the symbol is inconsistent across scopes.

Because we only need to fully check the files in the first scope, things go much quicker. The only downside is that the errors will be a little non-specific, eg. “Function `someFunc` is not compatible in `Component A` and `Component B`”, vs. if we fully validated, we know stuff like the properties of the return type of the function are different, and these are the differences, and it’s ok, or maybe a problem in the the context of the usage, etc.

markwpearce: Jellyfin_Roku validation:

Before:
```
Validate all scopes
Validate all scopes finished. (3s668.876ms)
Validation Metrics: filesValidated=307, fileValidationTime=236.509ms, fileInfoGenerationTime=91.165ms, programValidationTime=0.279ms, scopesValidated=138, totalLinkTime=681.913ms, totalScopeValidationTime=2s903.497ms, componentValidationTime=0.560ms
Validating project finished. (4s39.916ms)
```


After:
```
Validate all scopes
Validate all scopes finished. (1s447.582ms)
Validation Metrics: filesValidated=307, fileValidationTime=240.485ms, fileInfoGenerationTime=77.658ms, crossScopeValidationTime=78.467ms, scopesValidated=138, totalLinkTime=734.44ms, totalScopeValidationTime=637.439ms, componentValidationTime=0.558ms
Validating project finished. (1s885.752ms)
```

markwpearce: ⚡ 
markwpearce: 
https://github.com/rokucommunity/brighterscript/assets/810290/4e8a9540-c826-4b02-b39b-c34f2ac3c2ca



Patch:
diff --git a/scripts/scrape-roku-docs.ts b/scripts/scrape-roku-docs.ts
index e062ccc5..57854125 100644
--- a/scripts/scrape-roku-docs.ts
+++ b/scripts/scrape-roku-docs.ts
@@ -1175,7 +1175,7 @@ class Runner {
                         }
                     ],
                     interfaces: [],
-                    name: 'TrickPlayBar',
+                    name: 'ProgressBar',
                     url: 'https://developer.roku.com/en-ca/docs/references/scenegraph/media-playback-nodes/video.md#ui-fields'
                 }
             },
diff --git a/src/AstValidationSegmenter.ts b/src/AstValidationSegmenter.ts
index 6808de5e..04e38404 100644
--- a/src/AstValidationSegmenter.ts
+++ b/src/AstValidationSegmenter.ts
@@ -1,12 +1,14 @@
 import type { DottedGetExpression, TypeExpression, VariableExpression } from './parser/Expression';
-import { isBody, isClassStatement, isInterfaceStatement, isNamespaceStatement, isVariableExpression } from './astUtils/reflection';
+import { isAliasStatement, isBinaryExpression, isBody, isClassStatement, isDottedGetExpression, isInterfaceStatement, isNamespaceStatement, isTypeExpression, isVariableExpression } from './astUtils/reflection';
 import { ChildrenSkipper, WalkMode, createVisitor } from './astUtils/visitors';
-import type { GetTypeOptions, TypeChainEntry } from './interfaces';
-import type { AstNode } from './parser/AstNode';
+import type { ExtraSymbolData, GetTypeOptions, TypeChainEntry } from './interfaces';
+import type { AstNode, Expression } from './parser/AstNode';
 import { util } from './util';
-import type { NamespaceStatement } from './parser/Statement';
+import type { ClassStatement, NamespaceStatement } from './parser/Statement';
 import { SymbolTypeFlag } from './SymbolTypeFlag';
 import type { Token } from './lexer/Token';
+import type { BrsFile } from './files/BrsFile';
+import { TokenKind } from './lexer/TokenKind';
 
 // eslint-disable-next-line no-bitwise
 export const InsideSegmentWalkMode = WalkMode.visitStatements | WalkMode.visitExpressions | WalkMode.recurseChildFunctions;
@@ -16,6 +18,8 @@ export interface UnresolvedSymbol {
     flags: SymbolTypeFlag;
     endChainFlags: SymbolTypeFlag;
     containingNamespaces: string[];
+    file: BrsFile;
+    lookups: string[];
 }
 
 export interface AssignedSymbol {
@@ -32,6 +36,8 @@ export class AstValidationSegmenter {
     public assignedTokensInSegment = new Map<AstNode, Set<AssignedSymbol>>();
     public ast: AstNode;
 
+    constructor(public file: BrsFile) { }
+
     reset() {
         this.validatedSegments.clear();
         this.singleValidationSegments.clear();
@@ -53,12 +59,25 @@ export class AstValidationSegmenter {
         if (!expression) {
             return false;
         }
-        if (isVariableExpression(expression) && expression.tokens.name.text.toLowerCase() === 'm') {
-            return false;
+        let startOfDottedGet = expression as Expression;
+        while (isDottedGetExpression(startOfDottedGet)) {
+            startOfDottedGet = startOfDottedGet.obj;
+        }
+        if (isVariableExpression(startOfDottedGet)) {
+            const firstTokenTextLower = startOfDottedGet.tokens.name.text.toLowerCase();
+            if (firstTokenTextLower === 'm' || (this.currentClassStatement && firstTokenTextLower === 'super')) {
+                return false;
+            }
         }
+        if (isTypeExpression(expression) && isBinaryExpression(expression.expression)) {
+            return this.checkExpressionForUnresolved(segment, expression.expression.left as VariableExpression, assignedSymbolsNames) ||
+                this.checkExpressionForUnresolved(segment, expression.expression.right as VariableExpression, assignedSymbolsNames);
+        }
+
         const flag = util.isInTypeExpression(expression) ? SymbolTypeFlag.typetime : SymbolTypeFlag.runtime;
-        const typeChain: TypeChainEntry[] = [];
-        const options: GetTypeOptions = { flags: flag, onlyCacheResolvedTypes: true, typeChain: typeChain, data: {} };
+        let typeChain: TypeChainEntry[] = [];
+        const extraData = {} as ExtraSymbolData;
+        const options: GetTypeOptions = { flags: flag, onlyCacheResolvedTypes: true, typeChain: typeChain, data: extraData };
 
         const nodeType = expression.getType(options);
         if (!nodeType?.isResolvable()) {
@@ -71,7 +90,34 @@ export class AstValidationSegmenter {
                     symbolsSet = this.unresolvedSegmentsSymbols.get(segment);
                 }
                 this.validatedSegments.set(segment, false);
-                symbolsSet.add({ typeChain: typeChain, flags: typeChain[0].data.flags, endChainFlags: flag, containingNamespaces: this.currentNamespaceStatement?.getNameParts()?.map(t => t.text) });
+
+                if (extraData.isAlias && isAliasStatement(extraData.definingNode)) {
+                    //set the non-aliased version of this symbol as required.
+                    const aliasTypeChain = [];
+                    // eslint-disable-next-line no-bitwise
+                    extraData.definingNode.value.getType({ ...options, flags: SymbolTypeFlag.runtime | SymbolTypeFlag.typetime, typeChain: aliasTypeChain });
+                    typeChain = [...aliasTypeChain, ...typeChain.slice(1)];
+                }
+                const possibleNamespace = this.currentNamespaceStatement?.getNameParts()?.map(t => t.text)?.join('.').toLowerCase() ?? '';
+                const fullChainName = util.processTypeChain(typeChain).fullChainName?.toLowerCase();
+                const possibleNamesLower = [] as string[];
+                let lastSymbol = '';
+                for (const chainPart of fullChainName.split('.')) {
+                    lastSymbol += (lastSymbol ? `.${chainPart}` : chainPart);
+                    possibleNamesLower.push(lastSymbol);
+                    if (possibleNamespace) {
+                        possibleNamesLower.push(possibleNamespace + '.' + lastSymbol);
+                    }
+                }
+
+                symbolsSet.add({
+                    typeChain: typeChain,
+                    flags: typeChain[0].data.flags,
+                    endChainFlags: flag,
+                    containingNamespaces: this.currentNamespaceStatement?.getNameParts()?.map(t => t.text),
+                    file: this.file,
+                    lookups: possibleNamesLower
+                });
             }
             return true;
         }
@@ -79,11 +125,14 @@ export class AstValidationSegmenter {
     }
 
     private currentNamespaceStatement: NamespaceStatement;
+    private currentClassStatement: ClassStatement;
 
     checkSegmentWalk(segment: AstNode) {
         if (isNamespaceStatement(segment) || isBody(segment)) {
             return;
         }
+        this.currentNamespaceStatement = segment.findAncestor(isNamespaceStatement);
+
         if (isClassStatement(segment)) {
             if (segment.parentClassName) {
                 this.segmentsForValidation.push(segment.parentClassName);
@@ -113,12 +162,15 @@ export class AstValidationSegmenter {
         const skipper = new ChildrenSkipper();
         const assignedSymbols = new Set<AssignedSymbol>();
         const assignedSymbolsNames = new Set<string>();
-        this.currentNamespaceStatement = segment.findAncestor(isNamespaceStatement);
+        this.currentClassStatement = segment.findAncestor(isClassStatement);
 
         segment.walk(createVisitor({
             AssignmentStatement: (stmt) => {
-                assignedSymbols.add({ token: stmt.tokens.name, node: stmt });
-                assignedSymbolsNames.add(stmt.tokens.name.text.toLowerCase());
+                if (stmt.tokens.equals.kind === TokenKind.Equal) {
+                    // this is a straight assignment, not a compound assignment
+                    assignedSymbols.add({ token: stmt.tokens.name, node: stmt });
+                    assignedSymbolsNames.add(stmt.tokens.name.text.toLowerCase());
+                }
             },
             FunctionParameterExpression: (expr) => {
                 assignedSymbols.add({ token: expr.tokens.name, node: expr });
@@ -153,42 +205,33 @@ export class AstValidationSegmenter {
         if (!foundUnresolvedInSegment) {
             this.singleValidationSegments.add(segment);
         }
+        this.currentClassStatement = undefined;
+        this.currentClassStatement = undefined;
+
     }
 
+    getAllUnvalidatedSegments() {
+        const segmentsToWalkForValidation: AstNode[] = [];
+        for (const segment of this.segmentsForValidation) {
+            if (this.validatedSegments.get(segment)) {
+                continue;
+            }
+            segmentsToWalkForValidation.push(segment);
+        }
+        return segmentsToWalkForValidation;
+    }
 
-    getSegments(changedSymbols: Map<SymbolTypeFlag, Set<string>>): AstNode[] {
+    getSegmentsWithChangedSymbols(changedSymbols: Map<SymbolTypeFlag, Set<string>>): AstNode[] {
         const segmentsToWalkForValidation: AstNode[] = [];
-        const allChangedSymbolNames = [...changedSymbols.get(SymbolTypeFlag.runtime), ...changedSymbols.get(SymbolTypeFlag.typetime)];
         for (const segment of this.segmentsForValidation) {
+            if (this.validatedSegments.get(segment)) {
+                continue;
+            }
             const symbolsRequired = this.unresolvedSegmentsSymbols.get(segment);
-
-            const isSingleValidationSegment = this.singleValidationSegments.has(segment);
-            const singleValidationSegmentAlreadyValidated = isSingleValidationSegment ? this.validatedSegments.get(segment) : false;
-            let segmentNeedsRevalidation = !singleValidationSegmentAlreadyValidated;
-
             if (symbolsRequired) {
-                for (const requiredSymbol of symbolsRequired.values()) {
-                    for (const flagType of [SymbolTypeFlag.runtime, SymbolTypeFlag.typetime]) {
-                        // eslint-disable-next-line no-bitwise
-                        const runTimeOrTypeTimeSymbolFlag = requiredSymbol.flags & flagType;
-                        const changeSymbolSetForFlag = changedSymbols.get(runTimeOrTypeTimeSymbolFlag);
-                        if (!changeSymbolSetForFlag) {
-                            // This symbol has no flag - it is of unknown usage
-                            // This can happen when testing if a function exists
-                        } else if (util.setContainsUnresolvedSymbol(changeSymbolSetForFlag, requiredSymbol)) {
-                            segmentsToWalkForValidation.push(segment);
-                            break;
-                        }
-                    }
-                }
-            } else if (segmentNeedsRevalidation) {
-                segmentsToWalkForValidation.push(segment);
-            } else {
-                for (let assignedToken of this.assignedTokensInSegment?.get(segment)?.values() ?? []) {
-                    if (allChangedSymbolNames.includes(assignedToken.token.text.toLowerCase())) {
-                        segmentsToWalkForValidation.push(segment);
-                        break;
-                    }
+                if (util.hasAnyRequiredSymbolChanged([...symbolsRequired], changedSymbols)) {
+                    segmentsToWalkForValidation.push(segment);
+                    continue;
                 }
             }
         }
@@ -211,18 +254,10 @@ export class AstValidationSegmenter {
     }
 
 
-    checkIfSegmentNeedRevalidation(segment: AstNode) {
+    checkIfSegmentNeedsRevalidation(segment: AstNode, changedSymbols: Map<SymbolTypeFlag, Set<string>>) {
         if (!this.validatedSegments.get(segment)) {
             return true;
         }
-        const unresolved = this.unresolvedSegmentsSymbols.get(segment);
-        if (unresolved?.size > 0) {
-            return true;
-        } /*
-         const assignedTokens = this.assignedTokensInSegment.get(segment);
-         if (assignedTokens?.size > 0) {
-             return true;
-         }*/
         return false;
     }
 
diff --git a/src/CrossScopeValidator.spec.ts b/src/CrossScopeValidator.spec.ts
new file mode 100644
index 00000000..fa9b90f8
--- /dev/null
+++ b/src/CrossScopeValidator.spec.ts
@@ -0,0 +1,1634 @@
+import * as sinonImport from 'sinon';
+import { DiagnosticMessages } from './DiagnosticMessages';
+import { Program } from './Program';
+import * as path from 'path';
+import type { BrsFile } from './files/BrsFile';
+import { trim, expectZeroDiagnostics, expectDiagnostics, expectDiagnosticsIncludes } from './testHelpers.spec';
+import { expect } from 'chai';
+import { SymbolTypeFlag } from './SymbolTypeFlag';
+
+describe('CrossScopeValidator', () => {
+    let sinon = sinonImport.createSandbox();
+    let rootDir = process.cwd();
+    let program: Program;
+    beforeEach(() => {
+        program = new Program({
+            rootDir: rootDir
+        });
+        program.createSourceScope();
+    });
+    afterEach(() => {
+        sinon.restore();
+        program.dispose();
+    });
+
+    describe('providedNodes', () => {
+        it('builds a tree', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                sub callOutsideFunc()
+                    outsideFunc()
+                end sub
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                sub outsideFunc()
+                    print "hello"
+                end sub
+
+                class Klass
+                    x as integer
+                end class
+
+                interface IFace
+                    x as float
+                    y as float
+                end interface
+            `);
+            program.validate();
+            const sourceScope = program.getScopeByName('source');
+            const results = program.crossScopeValidation.getProvidedTree(sourceScope);
+            const tree = results.providedTree;
+            expect(tree.getSymbol('callOutsideFunc')).to.exist;
+            expect(tree.getSymbol('outsideFunc')).to.exist;
+            expect(tree.getSymbol('Klass')).to.exist;
+            expect(tree.getSymbol('IFace')).to.exist;
+        });
+
+        it('finds duplicates', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                namespace alpha.beta
+                    sub thing()
+                       alpha.outsideFunc()
+                    end sub
+                end namespace
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                namespace alpha
+                    sub outsideFunc()
+                        print "hello"
+                    end sub
+
+                    namespace beta
+                        class Thing
+                            x as integer
+                        end class
+                end namespace
+            `);
+            program.validate();
+            const sourceScope = program.getScopeByName('source');
+            const results = program.crossScopeValidation.getProvidedTree(sourceScope);
+            const tree = results.providedTree;
+            expect(tree.getSymbol('alpha.beta.Thing')).to.exist;
+            expect(results.duplicatesMap.has('alpha.beta.thing')).to.true;
+        });
+
+        it('builds a tree from namespaces', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                namespace alpha.beta
+                    sub callOutsideFunc()
+                       alpha.outsideFunc()
+                    end sub
+                end namespace
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                sub outsideFunc()
+                end sub
+
+                namespace alpha
+                    sub outsideFunc()
+                        print "hello"
+                    end sub
+
+                    namespace beta
+                        class Klass
+                            x as integer
+                        end class
+
+                        interface IFace
+                            x as float
+                            y as float
+                        end interface
+                    end namespace
+                end namespace
+            `);
+            program.validate();
+            const sourceScope = program.getScopeByName('source');
+            const results = program.crossScopeValidation.getProvidedTree(sourceScope);
+            const tree = results.providedTree;
+            expect(tree.getSymbol('alpha.beta.callOutsideFunc')).to.exist;
+            expect(tree.getSymbol('outsideFunc')).to.exist;
+            expect(tree.getSymbol('alpha.outsideFunc')).to.exist;
+            expect(tree.getSymbol('alpha.beta.Klass')).to.exist;
+            expect(tree.getSymbol('alpha.beta.IFace')).to.exist;
+        });
+    });
+
+    describe('provides & requires', () => {
+        it('finds a required symbol in another file', () => {
+            let file1 = program.setFile<BrsFile>('source/file1.bs', `
+                sub callOutsideFunc()
+                    outsideFunc()
+                end sub
+            `);
+            let file2 = program.setFile<BrsFile>('source/file2.bs', `
+                sub outsideFunc()
+                    print "hello"
+                end sub
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+            expect(file1.requiredSymbols.length).to.eq(1);
+            expect(file2.requiredSymbols.length).to.eq(0);
+            const sourceScopeIssues = program.crossScopeValidation.getIssuesForScope(program.getScopeByName('source'));
+            expect(sourceScopeIssues.missingSymbols.size).to.eq(0);
+        });
+
+        it('finds a required symbol in another file for each scope', () => {
+            let file1 = program.setFile<BrsFile>('source/file1.bs', `
+                sub callOutsideFunc()
+                    outsideFunc()
+                end sub
+            `);
+            let file2 = program.setFile<BrsFile>('source/file2.bs', `
+                sub outsideFunc()
+                    print "hello from source"
+                end sub
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            let widgetBs = program.setFile<BrsFile>('components/Widget.bs', `
+                sub init()
+                    callOutsideFunc()
+                end sub
+
+                sub outsideFunc()
+                    print "hello from widget"
+                end sub
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+            expect(file1.requiredSymbols.length).to.eq(1);
+            expect(file2.requiredSymbols.length).to.eq(0);
+            expect(widgetBs.requiredSymbols.length).to.eq(1);
+            const sourceScopeIssues = program.crossScopeValidation.getIssuesForScope(program.getScopeByName('source'));
+            expect(sourceScopeIssues.missingSymbols.size).to.eq(0);
+            const widgetScopeIssues = program.crossScopeValidation.getIssuesForScope(program.getScopeByName(`components${path.sep}Widget.xml`));
+            expect(widgetScopeIssues.missingSymbols.size).to.eq(0);
+        });
+
+        it('finds a required symbol in a namespace in another file', () => {
+            let file1 = program.setFile<BrsFile>('source/file1.bs', `
+                namespace alpha
+                    sub callOutsideFunc()
+                        outsideFunc()
+                    end sub
+                end namespace
+            `);
+            let file2 = program.setFile<BrsFile>('source/file2.bs', `
+                namespace alpha
+                    sub outsideFunc()
+                        print "hello from source"
+                    end sub
+                end namespace
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+            expect(file1.requiredSymbols.length).to.eq(1);
+            expect(file2.requiredSymbols.length).to.eq(0);
+            const sourceScopeIssues = program.crossScopeValidation.getIssuesForScope(program.getScopeByName('source'));
+            expect(sourceScopeIssues.missingSymbols.size).to.eq(0);
+        });
+
+
+        it('finds if a required symbol is defined different in different scopes', () => {
+            let file1 = program.setFile<BrsFile>('source/file1.bs', `
+                sub callOutsideFunc()
+                    print outsideFunc()
+                end sub
+            `);
+            let file2 = program.setFile<BrsFile>('source/file2.bs', `
+                function outsideFunc() as string
+                    return "hello from source"
+                end function
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            let widgetBs = program.setFile<BrsFile>('components/Widget.bs', `
+                sub init()
+                    print callOutsideFunc()
+                end sub
+
+                function outsideFunc() as integer
+                    return 123
+                end function
+            `);
+            program.validate();
+            //expectZeroDiagnostics(program);
+            expect(file1.requiredSymbols.length).to.eq(1);
+            expect(file2.requiredSymbols.length).to.eq(0);
+            expect(widgetBs.requiredSymbols.length).to.eq(1);
+            const incompatibleResolutions = program.crossScopeValidation.getIncompatibleSymbolResolutions();
+            expect(incompatibleResolutions.length).to.eq(1);
+            expect(incompatibleResolutions[0].incompatibleScopes.size).to.eq(2);
+        });
+
+        it('finds types defined in different file', () => {
+            let file1 = program.setFile<BrsFile>('source/file1.bs', `
+                function takesIface(z as MyInterface) as string
+                    return z.name
+                end function
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                interface MyInterface
+                    name as string
+                end interface
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+            expect(file1.requiredSymbols.length).to.eq(1);
+            expect(file1.requiredSymbols[0].flags).to.eq(SymbolTypeFlag.typetime);
+            expect(file1.requiredSymbols[0].typeChain[0].name).to.eq('MyInterface');
+            const sourceScopeIssues = program.crossScopeValidation.getIssuesForScope(program.getScopeByName('source'));
+            expect(sourceScopeIssues.missingSymbols.size).to.eq(0);
+        });
+
+        it('finds members of typecasts of types defined in different file', () => {
+            let file1 = program.setFile<BrsFile>('source/file1.bs', `
+                function takesIface(z) as string
+                    return (z as MyInterface).name
+                end function
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                interface MyInterface
+                    name as string
+                end interface
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+            expect(file1.requiredSymbols.length).to.eq(1);
+            expect(file1.requiredSymbols[0].flags).to.eq(SymbolTypeFlag.typetime);
+            expect(file1.requiredSymbols[0].typeChain[0].name).to.eq('MyInterface');
+            const sourceScopeIssues = program.crossScopeValidation.getIssuesForScope(program.getScopeByName('source'));
+            expect(sourceScopeIssues.missingSymbols.size).to.eq(0);
+        });
+    });
+
+    describe('incompatibleSymbolDefinition', () => {
+        it('allows different symbols that are compatible across scopes', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                function callsOther() as string
+                    return otherFunc()
+                end function
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                function otherFunc() as string
+                    return "hello"
+                end function
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            program.setFile<BrsFile>('components/Widget.bs', `
+                sub init()
+                    callsOther()
+                end sub
+
+                function otherFunc() as string ' same function signature as in file2.bs
+                    return "goodbye"
+                end function
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+
+        it('finds symbols inconsistent across scopes', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                function callsOther() as string
+                     otherFunc()
+                     return "test"
+                end function
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                function otherFunc() as string
+                    return "hello"
+                end function
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            program.setFile<BrsFile>('components/Widget.bs', `
+                sub init()
+                    callsOther()
+                end sub
+
+                function otherFunc() as integer
+                    return 42
+                end function
+            `);
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.incompatibleSymbolDefinition('otherFunc', `source, components${path.sep}Widget.xml`).message
+            ]);
+        });
+
+        it('finds namespaced symbols inconsistent across scopes', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                function callsAlphaBetaOther() as string
+                     alpha.beta.otherFunc()
+                     return "test"
+                end function
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                namespace alpha.beta
+                    function otherFunc() as string
+                        return "hello"
+                    end function
+                end namespace
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            program.setFile<BrsFile>('components/Widget.bs', `
+                sub init()
+                    callsAlphaBetaOther()
+                end sub
+
+                namespace alpha.beta
+                    function otherFunc() as integer
+                        return 42
+                    end function
+                end namespace
+            `);
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.incompatibleSymbolDefinition('alpha.beta.otherFunc', `source, components${path.sep}Widget.xml`).message
+            ]);
+        });
+
+        it('finds relative namespaced symbols inconsistent across scopes', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                namespace alpha.beta
+                    function callsOther() as string
+                        otherFunc()
+                        return "test"
+                    end function
+                end namespace
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                namespace alpha.beta
+                    function otherFunc() as string
+                        return "hello"
+                    end function
+                end namespace
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            program.setFile<BrsFile>('components/Widget.bs', `
+                sub init()
+                    alpha.beta.callsOther()
+                end sub
+
+                namespace alpha.beta
+                    function otherFunc() as integer
+                        return 42
+                    end function
+                end namespace
+            `);
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.incompatibleSymbolDefinition('otherFunc', `source, components${path.sep}Widget.xml`).message
+            ]);
+        });
+
+        it('adds a diagnostic when a file import changes', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                interface iface1
+                    name as string
+                    otherIface as iface2
+                end interface
+
+                function useIface1(x as iface1) as integer
+                    out = x.otherIface.data + 2
+                    return out
+                end function
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                interface iface2
+                    data as integer
+                end interface
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            program.setFile<BrsFile>('components/widget.bs', `
+                import "pkg:/source/file2.bs"
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            program.setFile<BrsFile>('components/file3.bs', `
+                interface iface2
+                    data as string
+                end interface
+            `);
+            program.setFile<BrsFile>('components/widget.bs', `
+                import "file3.bs"
+            `);
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.incompatibleSymbolDefinition('iface2', `source, components${path.sep}Widget.xml`).message
+            ]);
+        });
+    });
+
+    describe('cannotFindName', () => {
+        it('should not complain when all non-namespaced symbols are found', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                function callsOther() as string
+                    return otherFunc()
+                end function
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                function otherFunc() as string
+                    return "hello"
+                end function
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            program.setFile<BrsFile>('components/Widget.bs', `
+                sub init()
+                    callsOther()
+                end sub
+
+                function otherFunc() as string
+                    return "goodbye"
+                end function
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+        it('should not have scope specific error if symbol not found in any scope', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                function callsOther() as string
+                    return otherFunc()
+                end function
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            program.setFile<BrsFile>('components/Widget.bs', `
+                sub init()
+                    callsOther()
+                end sub
+            `);
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.cannotFindFunction('otherFunc').message
+            ]);
+        });
+
+        it('should allow namespaced symbols to match a require in a namespace', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                namespace alpha.beta
+                    function callsOther() as string
+                        return otherFunc()
+                    end function
+                end namespace
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                function otherFunc() as string ' non-namespaced - matches for scope source
+                    return "hello"
+                end function
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            program.setFile<BrsFile>('components/Widget.bs', `
+                sub init()
+                    alpha.beta.callsOther()
+                end sub
+
+                namespace alpha.beta
+                    function otherFunc() as string ' namespaced - matches for scope Widget
+                        return "goodbye"
+                    end function
+                end namespace
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+        it('should find when a non-namespaced symbols are not in a second scope', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                function callsOther() as string
+                    otherFunc()
+                    return "test"
+                end function
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                function otherFunc() as string
+                    return "hello"
+                end function
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            // "otherFunc" is in Widget
+            program.setFile<BrsFile>('components/Widget.bs', `
+                sub init()
+                    callsOther()
+                end sub
+
+                function otherFunc() as string
+                    return "goodbye"
+                end function
+            `);
+
+            program.setFile<BrsFile>('components/Widget2.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget2" extends="Group">
+                    <script uri="Widget2.bs"/>
+                    <script uri="pkg:/source/file1.bs"/>
+                </component>
+            `);
+            // "otherFunc" is NOT in Widget2
+            program.setFile<BrsFile>('components/Widget2.bs', `
+                sub init()
+                    callsOther()
+                end sub
+            `);
+            program.validate();
+
+            expectDiagnostics(program, [
+                DiagnosticMessages.cannotFindFunction('otherFunc', `components${path.sep}Widget2.xml`).message
+            ]);
+        });
+
+        it('should validate when type is not available in a second scope', () => {
+            program.setFile<BrsFile>('components/file1.bs', `
+                interface iface1 ' this file is in components - it is not in source scope
+                    data as iface2
+                end interface
+            `);
+            program.setFile<BrsFile>('components/file2.bs', `
+                interface iface2
+                    name as string
+                end interface
+            `);
+
+            program.setFile<BrsFile>('components/common.bs', `
+                sub printData(x as iface1)
+                    print x.data.name
+                end sub
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                </component>
+            `);
+            // "iface2" is in Widget
+            program.setFile<BrsFile>('components/Widget.bs', `
+                import "pkg:/components/file1.bs"
+                import "pkg:/components/file2.bs"
+                import "pkg:/components/common.bs"
+            `);
+
+            program.setFile<BrsFile>('components/Widget2.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget2" extends="Group">
+                    <script uri="Widget2.bs"/>
+                </component>
+            `);
+            // "iface2" is NOT in Widget2
+            program.setFile<BrsFile>('components/Widget2.bs', `
+                import "pkg:/components/file1.bs"
+                import "pkg:/components/common.bs"
+            `);
+            program.validate();
+            expectDiagnosticsIncludes(program, [
+                DiagnosticMessages.cannotFindName('iface2', `components${path.sep}Widget2.xml`).message
+            ]);
+        });
+
+        it('should validate when type is not available in an new scope', () => {
+            program.setFile<BrsFile>('components/file1.bs', `
+                interface iface1 ' this file is in components - it is not in source scope
+                    data as iface2
+                end interface
+            `);
+            program.setFile<BrsFile>('components/file2.bs', `
+                interface iface2
+                    name as string
+                end interface
+            `);
+
+            program.setFile<BrsFile>('components/common.bs', `
+                sub printData(x as iface1)
+                    print x.data.name
+                end sub
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                </component>
+            `);
+            // "iface2" is in Widget
+            program.setFile<BrsFile>('components/Widget.bs', `
+                import "pkg:/components/file1.bs"
+                import "pkg:/components/file2.bs"
+                import "pkg:/components/common.bs"
+            `);
+
+
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            program.setFile<BrsFile>('components/Widget2.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget2" extends="Group">
+                    <script uri="Widget2.bs"/>
+                </component>
+            `);
+            // "iface2" is NOT in Widget2
+            program.setFile<BrsFile>('components/Widget2.bs', `
+                import "pkg:/components/file1.bs"
+                import "pkg:/components/common.bs"
+            `);
+            program.validate();
+            expectDiagnosticsIncludes(program, [
+                DiagnosticMessages.cannotFindName('iface2', `components${path.sep}Widget2.xml`).message
+            ]);
+        });
+
+        it('should validate when namespaced symbol in second scope is missing because of file change', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                namespace alpha
+                    namespace beta
+                        function someFunc() as string
+                            return "hello"
+                        end function
+                    end namespace
+                end namespace
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                sub otherFunc()
+                    print alpha.beta.someFunc() + " world"
+                end sub
+            `);
+
+            program.setFile<BrsFile>('source/file3.bs', `
+                import "file1.bs"
+                import "file2.bs"
+            `);
+
+            program.setFile<BrsFile>('components/Widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="Widget.bs"/>
+                </component>
+            `);
+
+            program.setFile<BrsFile>('components/Widget.bs', `
+                import "pkg:/source/file3.bs"
+
+                sub init()
+                    alpha.beta.someFunc()
+                end sub
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            // change file3 so  alpha.beta.someFunc is not available
+            program.setFile<BrsFile>('pkg:/source/file3.bs', `
+                namespace alpha.beta ' need to define the namespace, but not someFunc()
+                    const pi = 3.14
+                end namespace
+            `);
+
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.cannotFindFunction('someFunc', 'alpha.beta.someFunc', 'alpha.beta', 'namespace').message
+            ]);
+        });
+
+        it('should find relative namespace items defined in another file', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                namespace alpha.beta
+                    enum Direction
+                        up
+                        down
+                    end enum
+
+                    class Foo
+                        x as integer
+                        dir as Direction
+                    end class
+                end namespace
+            `);
+            program.setFile<BrsFile>('source/file2.bs', `
+                namespace alpha.beta
+                    interface Data
+                        name as string
+                        id as integer
+                    end interface
+                end namespace
+            `);
+
+            program.setFile<BrsFile>('source/file3.bs', `
+                namespace Alpha.Beta
+                    class Bar extends Foo
+                        function getData() as Data
+                            return {name: m.dir.toStr(), id: m.x}
+                        end function
+                    end class
+                end namespace
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+
+        it('should find member symbols in other file when editing', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                namespace alpha.beta
+                    enum Direction
+                        up
+                        down
+                    end enum
+
+                    class Foo
+                         dir as Direction
+                    end class
+                end namespace
+            `);
+
+            program.setFile<BrsFile>('source/file2.bs', `
+                namespace Alpha.Beta
+                    class Bar extends Foo
+                        sub goDown()
+                            m.dir = Direction.down
+                        end sub
+                    end class
+                end namespace
+            `);
+
+            const file3Text = `
+                namespace Alpha.Beta
+                    class Other
+                        function getPi() as float
+                            return 3.14
+                        end function
+                    end class
+                end namespace
+            `;
+
+            program.setFile<BrsFile>('source/file3.bs', file3Text);
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            program.setFile<BrsFile>('source/file3.bs', file3Text); // NO CHANGE!!
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+        describe('const values', () => {
+            it('should allow const values to be composed of other const values from namespaces', () => {
+                program.setFile('source/constants.bs', `
+                    const top_pi = alpha.beta.pi
+                    const top_two = alpha.gamma.two
+                    const top_twoPi = top_two * top_pi
+                `);
+                program.setFile('source/ns.bs', `
+                    namespace alpha.beta
+                        const pi = 3.14
+                    end namespace
+
+                    namespace alpha.gamma
+                        const two = 2
+                    end namespace
+                `);
+
+                program.setFile('components/widget.xml', trim`
+                    <?xml version="1.0" encoding="utf-8" ?>
+                    <component name="Widget" extends="Group">
+                        <script uri="pkg:/components/widget.bs"/>
+                    </component>
+                `);
+
+                program.setFile('components/widget.bs', `
+                    import "pkg:/source/constants.bs"
+                    import "pkg:/source/ns.bs"
+
+                    sub init()
+                        print top_twoPi
+                    end sub
+                `);
+                program.validate();
+                expectZeroDiagnostics(program);
+            });
+        });
+
+        it('allows union types from imports', () => {
+            program.setFile('components/namespaces.bs', `
+                namespace alpha
+                    namespace beta
+                        interface BaseCase
+                            name as string
+                        end interface
+                    end namespace
+                end namespace
+
+            `);
+            program.setFile('components/utils.bs', `
+                namespace alpha
+                    namespace beta
+                        interface OtherCase
+                            name as string
+                        end interface
+                    end namespace
+                end namespace
+
+            `);
+            program.setFile('components/widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="pkg:/components/widget.bs"/>
+                </component>
+            `);
+
+            program.setFile('components/widget.bs', `
+                import "pkg:/components/utils.bs"
+                import "pkg:/components/namespaces.bs"
+
+                sub foo(input as alpha.beta.BaseCase or alpha.beta.OtherCase)
+                    print input.name
+                end sub
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+        it('finds custom components', () => {
+            program.setFile('components/utils.bs', `
+                function printWidgetName(widg as roSGNodeWidget)
+                    print widg.name
+                end function
+
+                function getWidget(name as string) as roSGNodeWidget
+                    widge = createObject("roSgNode", "Widget")
+                    widge.name = name
+                    return widge
+                end function
+            `);
+            program.setFile('components/widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="pkg:/components/widget.bs"/>
+                    <interface>
+                        <field id="name" type="string" />
+                    </interface>
+                </component>
+            `);
+
+            program.setFile('components/widget.bs', `
+                import "pkg:/components/utils.bs"
+
+                sub init()
+                    stuff(m.top as roSGNodeWidget)
+                end sub
+
+                sub stuff(w as roSgNodeWidget)
+                    printWidgetName(w)
+                end sub
+            `);
+
+            program.setFile('components/other.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Other" extends="Group">
+                    <script uri="pkg:/components/other.bs"/>
+                    <interface>
+                        <field id="name" type="string" />
+                    </interface>
+                </component>
+            `);
+
+            program.setFile('components/other.bs', `
+                import "pkg:/components/utils.bs"
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+    });
+
+    describe('alias statements', () => {
+        it('allows alias namespace and referencing it', () => {
+            program.setFile('components/utils.bs', `
+                namespace get
+                    function hello() as string
+                        return "hello"
+                    end function
+                end namespace
+            `);
+
+            program.setFile('components/widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="pkg:/components/widget.bs"/>
+                    <script uri="pkg:/components/utils.bs"/>
+                </component>
+            `);
+
+            program.setFile('components/widget.bs', `
+                alias get2 = get
+
+                sub foo()
+                    print get2.hello()
+                end sub
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+        it('gracefully handles circular reference', () => {
+            program.setFile('components/utils.bs', `
+                namespace get
+                    function aa()
+                        return {}
+                    end function
+                end namespace
+
+            `);
+
+            program.setFile('components/widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="pkg:/components/widget.bs"/>
+                    <script uri="pkg:/components/utils.bs"/>
+                </component>
+            `);
+
+            program.setFile('components/widget.bs', `
+                alias get2 = get2
+
+                sub foo()
+                    print "hello"
+                end sub
+            `);
+
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.cannotFindName('get2')
+            ]);
+            program.setFile('components/widget.bs', `
+                alias get2 = get 'no more circular reference
+
+                sub foo()
+                    print "hello"
+                end sub
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+        it('revalidates when source namespace changes', () => {
+            program.setFile('components/utils.bs', `
+                namespace alpha
+                    function passThru(val as integer) as string
+                        return val.toStr()
+                    end function
+                end namespace
+
+            `);
+
+            program.setFile('components/widget.xml', trim`
+                <?xml version="1.0" encoding="utf-8" ?>
+                <component name="Widget" extends="Group">
+                    <script uri="pkg:/components/widget.bs"/>
+                    <script uri="pkg:/components/utils.bs"/>
+                </component>
+            `);
+
+            program.setFile('components/widget.bs', `
+                alias beta = alpha
+
+                sub foo()
+                    print beta.passThru(1)
+                end sub
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            // now alpha.passthru takes a string
+            program.setFile('components/utils.bs', `
+                namespace alpha
+                    function passThru(val as string) as string
+                        return val.toStr()
+                    end function
+                end namespace
+
+            `);
+            program.validate();
+
+            expectDiagnostics(program, [
+                DiagnosticMessages.argumentTypeMismatch('integer', 'string').message
+            ]);
+        });
+    });
+
+    describe('revalidation', () => {
+        it('works for multi-scope usage, when variable is passed as function param', () => {
+            program.options.autoImportComponentScript = true;
+            //set a baseline where everyone is happy
+            program.setFile('components/TextOptions.bs', `
+                interface TextOptions
+                    fontName as string
+                end interface
+            `);
+
+            const button1 = program.setFile('components/Button1.bs', `
+                import "pkg:/components/TextOptions.bs"
+                function configure(options as TextOptions)
+                    m.fontName = options.fontName
+                end function
+            `);
+            program.setFile('components/Button1.xml', `
+                <component name="Button1" extends="Group">
+                </component>
+            `);
+
+            const button2 = program.setFile('components/Button2.bs', `
+                import "pkg:/components/TextOptions.bs"
+                function configure(options as TextOptions)
+                    m.fontName = options.fontName
+                end function
+            `);
+
+            program.setFile('components/Button2.xml', `
+                <component name="Button2" extends="Group">
+                </component>
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            //now rename the interface property and verify both files have an error
+            program.setFile('components/TextOptions.bs', `
+                interface TextOptions
+                    fontFileName as string 'renamed from "fontName" to "fontFileName"
+                end interface
+            `);
+            program.validate();
+            expectDiagnostics(program, [
+                {
+                    message: DiagnosticMessages.cannotFindName('fontName', 'TextOptions.fontName', 'TextOptions').message,
+                    file: {
+                        srcPath: button1.srcPath
+                    }
+                },
+                {
+                    message: DiagnosticMessages.cannotFindName('fontName', 'TextOptions.fontName', 'TextOptions').message,
+                    file: {
+                        srcPath: button2.srcPath
+                    }
+                }
+            ]);
+        });
+
+        it('works for multi-scope usage, with namespaced functions', () => {
+            program.options.autoImportComponentScript = true;
+            //set a baseline where everyone is happy
+            program.setFile('components/namespace.bs', `
+                namespace alpha
+                    namespace beta
+                        function getValue() as string
+                            return "value"
+                        end function
+                    end namespace
+                end namespace
+            `);
+
+            const button1 = program.setFile('components/Button1.bs', `
+                import "pkg:/components/namespace.bs"
+                function configure()
+                    m.label.text = alpha.beta.getValue()
+                end function
+            `);
+            program.setFile('components/Button1.xml', `
+                <component name="Button1" extends="Group">
+                </component>
+            `);
+
+            const button2 = program.setFile('components/Button2.bs', `
+                import "pkg:/components/namespace.bs"
+                namespace alpha.beta
+                    function configure()
+                        m.label.text = getValue()
+                    end function
+                end namespace
+            `);
+
+            program.setFile('components/Button2.xml', `
+                <component name="Button2" extends="Group">
+                </component>
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            //now rename the interface property and verify both files have an error
+            program.setFile('components/namespace.bs', `
+                namespace alpha
+                    namespace beta
+                        function getValue(text as string) as string
+                            return text
+                        end function
+                    end namespace
+                end namespace
+            `);
+            program.validate();
+            expectDiagnostics(program, [
+                {
+                    message: DiagnosticMessages.mismatchArgumentCount(1, 0).message,
+                    file: {
+                        srcPath: button1.srcPath
+                    }
+                },
+                {
+                    message: DiagnosticMessages.mismatchArgumentCount(1, 0).message,
+                    file: {
+                        srcPath: button2.srcPath
+                    }
+                }
+            ]);
+        });
+
+        it('works for multi-scope usage, with namespaced consts', () => {
+            program.options.autoImportComponentScript = true;
+            //set a baseline where everyone is happy
+            program.setFile('source/test.bs', `
+                namespace alpha.beta
+                   const PI = 3.14
+                end namespace
+            `);
+
+            program.setFile('components/Button1.bs', `
+                import "pkg:/source/test.bs"
+                function configure()
+                    m.label.text = alpha.beta.PI.toStr()
+                end function
+            `);
+            program.setFile('components/Button1.xml', `
+                <component name="Button1" extends="Group">
+                </component>
+            `);
+
+            program.setFile('components/Button2.bs', `
+                import "pkg:/source/test.bs"
+                namespace alpha.beta
+                    sub configure()
+                        m.label.text = alpha.beta.PI.toStr()
+                    end sub
+
+                    sub noop()
+                        print "hello"
+                    end sub
+                end namespace
+            `);
+
+            program.setFile('components/Button2.xml', `
+                <component name="Button2" extends="Group">
+                </component>
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            //now rename the interface property and verify both files have an error
+            program.setFile('source/test.bs', `
+                namespace alpha.beta
+                    const PI = 3.14159
+                end namespace
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+        it('works for multi-scope usage, with namespaced consts name changes', () => {
+            program.options.autoImportComponentScript = true;
+            program.logger.logLevel = 'info';
+            //set a baseline where everyone is happy
+            program.setFile('source/test.bs', `
+                namespace alpha.beta
+                   const PI = 3.14
+                end namespace
+            `);
+
+            for (let i = 0; i < 100; i++) {
+
+                program.setFile(`components/Button${i}.bs`, `
+                    import "pkg:/source/test.bs"
+                    namespace alpha.beta
+                        sub configure(x = alpha.beta.PI)
+                            print x
+                        end sub
+                    end namespace
+                `);
+
+                program.setFile(`components/Button${i}.xml`, `
+                    <component name="Button${i}" extends="Group">
+                    </component>
+                `);
+            }
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            //now rename the interface property and verify both files have an error
+            program.setFile('source/test.bs', `
+                namespace alpha.beta
+                    const PIE = 3.14159
+                end namespace
+            `);
+            program.validate();
+            expectDiagnosticsIncludes(program, [
+                DiagnosticMessages.cannotFindName('PI', 'alpha.beta.PI', 'alpha.beta', 'namespace').message
+            ]);
+        });
+
+        it('works for multi-scope usage, when changing imported namespace members', () => {
+            program.options.autoImportComponentScript = true;
+            //set a baseline where everyone is happy
+            program.setFile('source/test.bs', `
+                namespace alpha.beta
+                   function getValue()
+                        return 1
+                    end function
+
+                    namespace charlie
+                        const hello = "hello"
+                    end namespace
+                end namespace
+            `);
+
+            const button1 = program.setFile('components/Button1.bs', `
+                import "pkg:/source/test.bs"
+                function configure()
+                    m.label.text = alpha.beta.getValue.toStr()
+                end function
+
+                sub noop()
+                    print alpha.beta.charlie.hello
+                end sub
+            `);
+            program.setFile('components/Button1.xml', `
+                <component name="Button1" extends="Group">
+                </component>
+            `);
+
+            const button2 = program.setFile('components/Button2.bs', `
+                import "pkg:/source/test.bs"
+                namespace alpha.beta
+                    sub configure()
+                        m.label.text = getValue.toStr()
+                    end sub
+
+                    sub noop()
+                        print "hello"
+                    end sub
+
+                    sub noop2()
+                        print alpha.beta.charlie.hello
+                    end sub
+                end namespace
+            `);
+
+            program.setFile('components/Button2.xml', `
+                <component name="Button2" extends="Group">
+                </component>
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            //now rename the interface property and verify both files have an error
+            program.setFile('source/test.bs', `
+                namespace alpha.beta
+                    function getOtherValue()
+                        return 1
+                    end function
+
+                    namespace charlie
+                        const hello = "hello"
+                    end namespace
+                end namespace
+            `);
+            program.validate();
+            expectDiagnostics(program, [
+                {
+                    message: DiagnosticMessages.cannotFindName('getValue', 'alpha.beta.getValue', 'alpha.beta', 'namespace').message,
+                    file: {
+                        srcPath: button1.srcPath
+                    }
+                },
+                {
+                    message: DiagnosticMessages.cannotFindName('getValue').message,
+                    file: {
+                        srcPath: button2.srcPath
+                    }
+                }
+            ]);
+        });
+
+        it('works when changing file multiple times', () => {
+            program.options.autoImportComponentScript = true;
+            //set a baseline where everyone is happy
+            program.setFile('source/test.bs', `
+                namespace alpha.beta
+                    function someFunc()
+                        return "test"
+                    end function
+                end namespace
+            `);
+
+            const widgetFile = program.setFile('components/Widget.bs', `
+                import "pkg:/source/test.bs"
+                sub init()
+                    alpha.beta.someFunc()
+                end sub
+            `);
+            program.setFile('components/Widget.xml', `
+                <component name="Widget" extends="Group">
+                </component>
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            let testCount = 4;
+            while (testCount > 0) {
+                testCount--;
+
+                //now rename the interface property and verify both files have an error
+                program.setFile('source/test.bs', `
+                    namespace alpha.beta
+                        function notSomeFunc()
+                            return "test"
+                        end function
+                    end namespace
+                `);
+                program.validate();
+                expectDiagnostics(program, [
+                    {
+                        message: DiagnosticMessages.cannotFindFunction('someFunc', 'alpha.beta.someFunc', 'alpha.beta', 'namespace').message,
+                        file: {
+                            srcPath: widgetFile.srcPath
+                        }
+                    }
+                ]);
+
+                //reset file:
+                program.setFile('source/test.bs', `
+                    namespace alpha.beta
+                        function someFunc()
+                            return "test"
+                        end function
+                    end namespace
+                `);
+                program.validate();
+                expectZeroDiagnostics(program);
+            }
+        });
+    });
+
+    describe('duplicate symbols / name collisions', () => {
+        it('finds duplicates', () => {
+            program.options.autoImportComponentScript = true;
+
+            program.setFile('source/utils.bs', `
+                interface Test
+                    name as string
+                end interface
+            `);
+
+            program.setFile('components/Button1.bs', `
+                import "pkg:/source/utils.bs"
+                const Test = "test"
+            `);
+            program.setFile('components/Button1.xml', `
+                <component name="Button1" extends="Group">
+                </component>
+            `);
+
+            program.setFile('components/Button2.bs', `
+                import "pkg:/source/utils.bs"
+                function test()
+                end function
+            `);
+
+            program.setFile('components/Button2.xml', `
+                <component name="Button2" extends="Group">
+                </component>
+            `);
+
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.nameCollision('Const', 'Interface', 'Test'),
+                DiagnosticMessages.nameCollision('Interface', 'Const', 'Test'),
+                DiagnosticMessages.nameCollision('Function', 'Interface', 'Test'),
+                DiagnosticMessages.nameCollision('Interface', 'Function', 'test')
+            ]);
+        });
+
+        it('finds duplicates inside a namespace', () => {
+            program.options.autoImportComponentScript = true;
+
+            program.setFile('source/utils.bs', `
+                namespace alpha.beta
+                    interface Test
+                        name as string
+                    end interface
+                end namespace
+            `);
+
+            program.setFile('components/Button1.bs', `
+                import "pkg:/source/utils.bs"
+                namespace alpha.beta
+                    const Test = "test"
+                end namespace
+            `);
+            program.setFile('components/Button1.xml', `
+                <component name="Button1" extends="Group">
+                </component>
+            `);
+
+            program.setFile('components/Button2.bs', `
+                import "pkg:/source/utils.bs"
+                namespace alpha.beta
+                    function test()
+                    end function
+                end namespace
+            `);
+
+            program.setFile('components/Button2.xml', `
+                <component name="Button2" extends="Group">
+                </component>
+            `);
+
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.nameCollision('Const', 'Interface', 'alpha.beta.Test'),
+                DiagnosticMessages.nameCollision('Interface', 'Const', 'alpha.beta.Test'),
+                DiagnosticMessages.nameCollision('Function', 'Interface', 'alpha.beta.Test'),
+                DiagnosticMessages.nameCollision('Interface', 'Function', 'alpha.beta.test')
+            ]);
+        });
+
+        it('does not have diagnostic if items are namespaced', () => {
+            program.options.autoImportComponentScript = true;
+
+            program.setFile('source/utils.bs', `
+                interface Test
+                    name as string
+                end interface
+            `);
+
+            program.setFile('components/Button1.bs', `
+                import "pkg:/source/utils.bs"
+                namespace alpha.beta
+                    const Test = "test"
+                end namespace
+            `);
+            program.setFile('components/Button1.xml', `
+                <component name="Button1" extends="Group">
+                </component>
+            `);
+
+            program.setFile('components/Button2.bs', `
+                import "pkg:/source/utils.bs"
+                namespace alpha.beta
+                    function test()
+                    end function
+                end namespace
+            `);
+
+            program.setFile('components/Button2.xml', `
+                <component name="Button2" extends="Group">
+                </component>
+            `);
+
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+        it('finds duplicates in same file', () => {
+            program.options.autoImportComponentScript = true;
+
+            program.setFile('source/utils.bs', `
+                interface Test
+                    name as string
+                end interface
+
+                const Test = 89
+            `);
+
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.nameCollision('Const', 'Interface', 'Test'),
+                DiagnosticMessages.nameCollision('Interface', 'Const', 'Test')
+            ]);
+        });
+
+        it('finds namespaced duplicates in same file', () => {
+            program.options.autoImportComponentScript = true;
+
+            program.setFile('source/utils.bs', `
+                namespace alpha
+                    namespace beta
+                        interface Test
+                            name as string
+                        end interface
+
+                        const Test = 89
+                    end namespace
+                end namespace
+            `);
+
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.nameCollision('Const', 'Interface', 'alpha.beta.Test'),
+                DiagnosticMessages.nameCollision('Interface', 'Const', 'alpha.beta.Test')
+            ]);
+        });
+    });
+});
diff --git a/src/CrossScopeValidator.ts b/src/CrossScopeValidator.ts
new file mode 100644
index 00000000..1d664273
--- /dev/null
+++ b/src/CrossScopeValidator.ts
@@ -0,0 +1,711 @@
+import type { UnresolvedSymbol } from './AstValidationSegmenter';
+import type { Scope } from './Scope';
+import type { BrsFile, ProvidedSymbol } from './files/BrsFile';
+import { DiagnosticMessages } from './DiagnosticMessages';
+import type { Program } from './Program';
+import util from './util';
+import { SymbolTypeFlag } from './SymbolTypeFlag';
+import type { BscSymbol } from './SymbolTable';
+import { isCallExpression, isConstStatement, isEnumStatement, isEnumType, isFunctionStatement, isInheritableType, isInterfaceStatement, isNamespaceStatement, isNamespaceType, isReferenceType, isTypedFunctionType, isUnionType } from './astUtils/reflection';
+import type { ReferenceType } from './types/ReferenceType';
+import { getAllRequiredSymbolNames } from './types/ReferenceType';
+import type { TypeChainEntry, TypeChainProcessResult } from './interfaces';
+import { BscTypeKind } from './types/BscTypeKind';
+import { getAllTypesFromUnionType } from './types/helpers';
+import type { BscType } from './types/BscType';
+import type { BscFile } from './files/BscFile';
+import type { NamespaceStatement } from './parser/Statement';
+import { ParseMode } from './parser/Parser';
+import { URI } from 'vscode-uri';
+import { globalFile } from './globalCallables';
+
+
+interface FileSymbolPair {
+    file: BscFile;
+    symbol: BscSymbol;
+}
+
+interface SymbolLookupKeys {
+    potentialTypeKey: string;
+    key: string;
+    namespacedKey: string;
+    namespacedPotentialTypeKey: string;
+}
+
+const CrossScopeValidatorDiagnosticTag = 'CrossScopeValidator';
+
+export class ProvidedNode {
+
+    namespaces = new Map<string, ProvidedNode>();
+    symbols = new Map<string, FileSymbolPair>();
+
+    constructor(public key: string = '', private componentsMap?: Map<string, FileSymbolPair>) { }
+
+
+    getSymbolByKey(symbolKeys: SymbolLookupKeys): FileSymbolPair {
+        return this.getSymbol(symbolKeys.namespacedKey) ??
+            this.getSymbol(symbolKeys.key) ??
+            this.getSymbol(symbolKeys.namespacedPotentialTypeKey) ??
+            this.getSymbol(symbolKeys.potentialTypeKey);
+    }
+
+    getSymbol(symbolName: string): FileSymbolPair {
+        if (!symbolName) {
+            return;
+        }
+        const lowerSymbolName = symbolName.toLowerCase();
+        if (this.componentsMap?.has(lowerSymbolName)) {
+            return this.componentsMap.get(lowerSymbolName);
+        }
+        let lowerSymbolNameParts = lowerSymbolName.split('.');
+        return this.getSymbolByNameParts(lowerSymbolNameParts, this);
+    }
+
+    getNamespace(namespaceName: string): ProvidedNode {
+        let lowerSymbolNameParts = namespaceName.toLowerCase().split('.');
+        return this.getNamespaceByNameParts(lowerSymbolNameParts);
+    }
+
+    getSymbolByNameParts(lowerSymbolNameParts: string[], root: ProvidedNode): FileSymbolPair {
+        const first = lowerSymbolNameParts?.[0];
+        const rest = lowerSymbolNameParts.slice(1);
+        if (!first) {
+            return;
+        }
+        if (this.symbols.has(first)) {
+            let result = this.symbols.get(first);
+            let currentType = result.symbol.type;
+
+            for (const namePart of rest) {
+                if (isTypedFunctionType(currentType)) {
+                    const returnType = currentType.returnType;
+                    if (returnType.isResolvable()) {
+                        currentType = returnType;
+                    } else if (isReferenceType(returnType)) {
+                        const fullName = returnType.fullName;
+                        if (fullName.includes('.')) {
+                            currentType = root.getSymbol(fullName)?.symbol?.type;
+                        } else {
+                            currentType = this.getSymbol(fullName)?.symbol?.type ??
+                                root.getSymbol(fullName)?.symbol?.type;
+                        }
+                    }
+                }
+                let typesToTry = [currentType];
+                if (isEnumType(currentType)) {
+                    typesToTry.push(currentType.defaultMemberType);
+                }
+                if (isInheritableType(currentType)) {
+                    let inheritableType = currentType as any;
+                    while (inheritableType?.parentType) {
+                        let parentType = inheritableType.parentType;
+                        if (isReferenceType(inheritableType.parentType)) {
+                            const fullName = inheritableType.parentType.fullName;
+                            if (fullName.includes('.')) {
+                                parentType = root.getSymbol(fullName)?.symbol?.type;
+                            } else {
+                                parentType = this.getSymbol(fullName)?.symbol?.type ??
+                                    root.getSymbol(fullName)?.symbol?.type;
+                            }
+                        }
+                        typesToTry.push(parentType);
+                        inheritableType = parentType;
+                    }
+
+                }
+                const extraData = {};
+
+                for (const curType of typesToTry) {
+                    currentType = curType?.getMemberType(namePart, { flags: SymbolTypeFlag.runtime, data: extraData });
+                    if (isReferenceType(currentType)) {
+                        const memberLookup = currentType.fullName;
+                        currentType = this.getSymbol(memberLookup.toLowerCase())?.symbol?.type ?? root.getSymbol(memberLookup.toLowerCase())?.symbol?.type;
+                    }
+                    if (currentType) {
+                        break;
+                    }
+                }
+
+                if (!currentType) {
+                    return;
+                }
+                // get specific member
+                result = {
+                    ...result, symbol: { name: namePart, type: currentType, data: extraData, flags: SymbolTypeFlag.runtime }
+                };
+            }
+            return result;
+
+        } else if (rest && this.namespaces.has(first)) {
+            const node = this.namespaces.get(first);
+            const parts = node.getSymbolByNameParts(rest, root);
+
+            return parts;
+        }
+    }
+
+    getNamespaceByNameParts(lowerSymbolNameParts: string[]): ProvidedNode {
+        const first = lowerSymbolNameParts?.[0]?.toLowerCase();
+        const rest = lowerSymbolNameParts.slice(1);
+        if (!first) {
+            return;
+        }
+        if (this.namespaces.has(first)) {
+            const node = this.namespaces.get(first);
+            const result = rest?.length > 0 ? node.getNamespaceByNameParts(rest) : node;
+            return result;
+        }
+    }
+
+    addSymbol(symbolName: string, symbolPair: FileSymbolPair) {
+        let lowerSymbolNameParts = symbolName.toLowerCase().split('.');
+        return this.addSymbolByNameParts(lowerSymbolNameParts, symbolPair);
+    }
+
+    private addSymbolByNameParts(lowerSymbolNameParts: string[], symbolPair: FileSymbolPair) {
+        const first = lowerSymbolNameParts?.[0];
+        const rest = lowerSymbolNameParts?.slice(1);
+        let isDuplicate = false;
+        if (!first) {
+            return;
+        }
+        if (rest?.length > 0) {
+            // first must be a namespace
+            let namespaceNode = this.namespaces.get(first);
+            if (!namespaceNode) {
+                namespaceNode = new ProvidedNode(first);
+                this.namespaces.set(first, namespaceNode);
+            }
+            return namespaceNode.addSymbolByNameParts(rest, symbolPair);
+        } else {
+            if (this.namespaces.get(first)) {
+                // trying to add a symbol that already exists as a namespace - this is a duplicate
+                return true;
+            }
+
+            // just add it to the symbols
+            const existingSymbolPair = this.symbols.get(first);
+            if (!existingSymbolPair) {
+                this.symbols.set(first, symbolPair);
+            } else {
+                isDuplicate = existingSymbolPair.symbol.data?.definingNode !== symbolPair.symbol.data?.definingNode;
+            }
+        }
+        return isDuplicate;
+    }
+}
+
+
+export class CrossScopeValidator {
+
+    constructor(public program: Program) { }
+
+    private symbolMapKeys(symbol: UnresolvedSymbol): SymbolLookupKeys[] {
+        let keysArray = new Array<SymbolLookupKeys>();
+        let unnamespacedNameLowers: string[] = [];
+
+        function joinTypeChainForKey(typeChain: TypeChainEntry[], firstType?: BscType) {
+            firstType ||= typeChain[0].type;
+            const unnamespacedNameLower = typeChain.map((tce, i) => {
+                if (i === 0) {
+                    if (isReferenceType(firstType)) {
+                        return firstType.fullName;
+                    } else if (isInheritableType(firstType)) {
+                        return tce.type.toString();
+                    }
+                    return tce.name;
+                }
+                return tce.name;
+            }).join('.').toLowerCase();
+            return unnamespacedNameLower;
+        }
+
+        if (isUnionType(symbol.typeChain[0].type) && symbol.typeChain[0].data.isInstance) {
+            const allUnifiedTypes = getAllTypesFromUnionType(symbol.typeChain[0].type);
+            for (const unifiedType of allUnifiedTypes) {
+                unnamespacedNameLowers.push(joinTypeChainForKey(symbol.typeChain, unifiedType));
+            }
+
+        } else {
+            unnamespacedNameLowers.push(joinTypeChainForKey(symbol.typeChain));
+        }
+
+        for (const unnamespacedNameLower of unnamespacedNameLowers) {
+            const lowerFirst = symbol.typeChain[0]?.name?.toLowerCase() ?? '';
+            let namespacedName = '';
+            let lowerNamespacePrefix = '';
+            let namespacedPotentialTypeKey = '';
+            if (symbol.containingNamespaces?.length > 0 && symbol.typeChain[0]?.name.toLowerCase() !== symbol.containingNamespaces[0].toLowerCase()) {
+                lowerNamespacePrefix = `${(symbol.containingNamespaces ?? []).join('.')}`.toLowerCase();
+            }
+            if (lowerNamespacePrefix) {
+                namespacedName = `${lowerNamespacePrefix}.${unnamespacedNameLower}`;
+                namespacedPotentialTypeKey = `${lowerNamespacePrefix}.${lowerFirst}`;
+            }
+
+            keysArray.push({
+                potentialTypeKey: lowerFirst, // first entry in type chain (useful for enum types, typecasts, etc.)
+                key: unnamespacedNameLower, //full name used in code (useful for namespaced symbols)
+                namespacedKey: namespacedName, // full name including namespaces (useful for relative symbols in a namespace)
+                namespacedPotentialTypeKey: namespacedPotentialTypeKey //first entry in chain, prefixed with current namespace
+            });
+        }
+        return keysArray;
+    }
+
+    resolutionsMap = new Map<UnresolvedSymbol, Set<{ scope: Scope; sourceFile: BscFile; providedSymbol: BscSymbol }>>();
+    providedTreeMap = new Map<Scope, { duplicatesMap: Map<string, Set<FileSymbolPair>>; providedTree: ProvidedNode }>();
+
+
+    private componentsMap = new Map<string, FileSymbolPair>();
+
+    getRequiredMap(scope: Scope) {
+        const map = new Map<SymbolLookupKeys, UnresolvedSymbol>();
+        scope.enumerateBrsFiles((file) => {
+            for (const symbol of file.requiredSymbols) {
+                const symbolKeysArray = this.symbolMapKeys(symbol);
+                for (const symbolKeys of symbolKeysArray) {
+                    map.set(symbolKeys, symbol);
+                }
+            }
+        });
+        return map;
+    }
+
+    getProvidedTree(scope: Scope) {
+        if (this.providedTreeMap.has(scope)) {
+            return this.providedTreeMap.get(scope);
+        }
+        const providedTree = new ProvidedNode('', this.componentsMap);
+        const duplicatesMap = new Map<string, Set<FileSymbolPair>>();
+
+        const referenceTypesMap = new Map<{ symbolName: string; file: BscFile; symbolObj: ProvidedSymbol }, Array<{ name: string; namespacedName?: string }>>();
+
+
+        const addSymbolWithDuplicates = (symbolName: string, file: BscFile, symbolObj: ProvidedSymbol) => {
+            // eslint-disable-next-line no-bitwise
+            const globalSymbol = this.program.globalScope.symbolTable.getSymbol(symbolName, SymbolTypeFlag.typetime | SymbolTypeFlag.runtime);
+            const symbolIsNamespace = providedTree.getNamespace(symbolName);
+            const isDupe = providedTree.addSymbol(symbolName, { file: file, symbol: symbolObj.symbol });
+            if (symbolIsNamespace || globalSymbol || isDupe || symbolObj.duplicates.length > 0) {
+                let dupesSet = duplicatesMap.get(symbolName);
+                if (!dupesSet) {
+                    dupesSet = new Set<{ file: BrsFile; symbol: BscSymbol }>();
+                    duplicatesMap.set(symbolName, dupesSet);
+                    const existing = providedTree.getSymbol(symbolName);
+                    if (existing) {
+                        dupesSet.add(existing);
+                    }
+                }
+                if (!dupesSet.has({ file: file, symbol: symbolObj.symbol })) {
+                    dupesSet.add({ file: file, symbol: symbolObj.symbol });
+                }
+                if (symbolIsNamespace) {
+                    const namespaceContainer = scope.getNamespace(symbolName);
+                    const nsNode = namespaceContainer?.namespaceStatements?.[0];
+                    if (nsNode) {
+                        const nsFile = namespaceContainer.file;
+                        const nsType = nsNode.getType({ flags: SymbolTypeFlag.typetime });
+                        let nsSymbol: BscSymbol = {
+                            name: nsNode.getName(ParseMode.BrighterScript),
+                            type: nsType,
+                            data: { definingNode: nsNode },
+                            flags: SymbolTypeFlag.typetime
+                        };
+                        if (nsSymbol && !dupesSet.has({ file: nsFile, symbol: nsSymbol })) {
+                            dupesSet.add({ file: nsFile, symbol: nsSymbol });
+                        }
+                    }
+                }
+                for (const providedDupeSymbol of symbolObj.duplicates) {
+                    if (!dupesSet.has({ file: file, symbol: providedDupeSymbol })) {
+                        dupesSet.add({ file: file, symbol: providedDupeSymbol });
+                    }
+                }
+                if (globalSymbol) {
+                    dupesSet.add({ file: globalFile, symbol: globalSymbol[0] });
+                }
+            }
+        };
+
+        scope.enumerateBrsFiles((file) => {
+            for (const [_, nameMap] of file.providedSymbols.symbolMap.entries()) {
+
+                for (const [symbolName, symbolObj] of nameMap.entries()) {
+                    if (isNamespaceType(symbolObj.symbol.type)) {
+                        continue;
+                    }
+                    addSymbolWithDuplicates(symbolName, file, symbolObj);
+                }
+            }
+
+            // find all "provided symbols" that are reference types
+            for (const [_, nameMap] of file.providedSymbols.referenceSymbolMap.entries()) {
+                for (const [symbolName, symbolObj] of nameMap.entries()) {
+                    const symbolType = symbolObj.symbol.type;
+                    const namespaceLower = symbolObj.symbol.data?.definingNode?.findAncestor<NamespaceStatement>(isNamespaceStatement)?.getName(ParseMode.BrighterScript).toLowerCase();
+                    const allNames = getAllRequiredSymbolNames(symbolType, namespaceLower);
+
+                    referenceTypesMap.set({ symbolName: symbolName, file: file, symbolObj: symbolObj }, allNames);
+                }
+            }
+        });
+
+        // check provided reference types to see if they exist yet!
+        while (referenceTypesMap.size > 0) {
+            let addedSymbol = false;
+            for (const [refTypeDetails, neededNames] of referenceTypesMap.entries()) {
+                let foundNames = 0;
+                for (const neededName of neededNames) {
+                    // check if name exists or namespaced version exists
+                    if (providedTree.getSymbol(neededName.name) ?? providedTree.getSymbol(neededName.namespacedName)) {
+                        foundNames++;
+                    }
+                }
+                if (neededNames.length === foundNames) {
+                    //found all that were needed
+                    addSymbolWithDuplicates(refTypeDetails.symbolName, refTypeDetails.file, refTypeDetails.symbolObj);
+                    referenceTypesMap.delete(refTypeDetails);
+                    addedSymbol = true;
+                }
+            }
+            if (!addedSymbol) {
+                break;
+            }
+        }
+
+        const result = { duplicatesMap: duplicatesMap, providedTree: providedTree };
+        this.providedTreeMap.set(scope, result);
+        return result;
+    }
+
+    getIssuesForScope(scope: Scope) {
+        const requiredMap = this.getRequiredMap(scope);
+        const { providedTree, duplicatesMap } = this.getProvidedTree(scope);
+
+        const missingSymbols = new Set<UnresolvedSymbol>();
+
+        for (const [symbolKeys, unresolvedSymbol] of requiredMap.entries()) {
+
+            // check global scope for components
+            if (unresolvedSymbol.typeChain.length === 1 && this.program.globalScope.symbolTable.getSymbol(unresolvedSymbol.typeChain[0].name, unresolvedSymbol.flags)) {
+                //symbol is available in global scope. ignore it
+                continue;
+            }
+            let foundSymbol = providedTree.getSymbolByKey(symbolKeys);
+
+            if (foundSymbol) {
+                if (!unresolvedSymbol.typeChain[0].data?.isInstance) {
+                    let resolvedListForSymbol = this.resolutionsMap.get(unresolvedSymbol);
+                    if (!resolvedListForSymbol) {
+                        resolvedListForSymbol = new Set<{ scope: Scope; sourceFile: BrsFile; providedSymbol: BscSymbol }>();
+                        this.resolutionsMap.set(unresolvedSymbol, resolvedListForSymbol);
+                    }
+                    resolvedListForSymbol.add({
+                        scope: scope,
+                        sourceFile: foundSymbol.file,
+                        providedSymbol: foundSymbol.symbol
+                    });
+                }
+            } else {
+                let foundNamespace = providedTree.getNamespace(symbolKeys.key);
+
+                if (foundNamespace) {
+                    // this symbol turned out to be a namespace. This is allowed for alias statements
+                    // TODO: add check to make sure this usage is from an alias statement
+                } else {
+                    // did not find symbol!
+                    const missing = { ...unresolvedSymbol };
+                    let namespaceNode = providedTree;
+                    let currentKnownType;
+                    for (const chainEntry of missing.typeChain) {
+                        if (!chainEntry.isResolved) {
+                            // for each unresolved part of a chain, see if we can resolve it with stuff from the provided tree
+                            // and if so, mark it as resolved
+                            const lookupName = (chainEntry.type as ReferenceType)?.fullName ?? chainEntry.name;
+                            if (!currentKnownType) {
+                                namespaceNode = namespaceNode?.getNamespaceByNameParts([chainEntry.name]);
+
+                            }
+                            if (namespaceNode) {
+                                chainEntry.isResolved = true;
+                            } else {
+                                if (currentKnownType) {
+                                    currentKnownType = currentKnownType.getMemberType(chainEntry.name, { flags: SymbolTypeFlag.runtime });
+                                } else {
+                                    currentKnownType = providedTree.getSymbol(lookupName.toLowerCase())?.symbol?.type;
+                                }
+
+                                if (currentKnownType?.isResolvable()) {
+                                    chainEntry.isResolved = true;
+                                } else {
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    missingSymbols.add(unresolvedSymbol);
+                }
+            }
+        }
+        return { missingSymbols: missingSymbols, duplicatesMap: duplicatesMap };
+    }
+
+    clearResolutionsForFile(file: BrsFile) {
+        for (const symbol of this.resolutionsMap.keys()) {
+            if (symbol.file === file) {
+                this.resolutionsMap.delete(symbol);
+            }
+        }
+    }
+
+    clearResolutionsForScopes(scopes: Scope[]) {
+        for (const [symbol, resolutionInfos] of this.resolutionsMap.entries()) {
+            for (const info of resolutionInfos.values()) {
+                if (scopes.includes(info.scope)) {
+                    resolutionInfos.delete(info);
+                }
+            }
+            if (resolutionInfos.size === 0) {
+                this.resolutionsMap.delete(symbol);
+            }
+        }
+    }
+
+    getFilesRequiringChangedSymbol(scopes: Scope[], changedSymbols: Map<SymbolTypeFlag, Set<string>>) {
+        const filesThatNeedRevalidation = new Set<BscFile>();
+        const filesThatDoNotNeedRevalidation = new Set<BscFile>();
+
+        for (const scope of scopes) {
+            scope.enumerateBrsFiles((file) => {
+                if (filesThatNeedRevalidation.has(file) || filesThatDoNotNeedRevalidation.has(file)) {
+                    return;
+                }
+                if (util.hasAnyRequiredSymbolChanged(file.requiredSymbols, changedSymbols)) {
+                    filesThatNeedRevalidation.add(file);
+                    return;
+                }
+                filesThatDoNotNeedRevalidation.add(file);
+            });
+        }
+        return filesThatNeedRevalidation;
+    }
+
+    buildComponentsMap() {
+        this.componentsMap.clear();
+        // Add custom components
+        for (let componentName of this.program.getSortedComponentNames()) {
+            const typeName = 'rosgnode' + componentName;
+            const component = this.program.getComponent(componentName);
+            const componentSymbol = this.program.globalScope.symbolTable.getSymbol(typeName, SymbolTypeFlag.typetime)?.[0];
+            if (componentSymbol && component) {
+                this.componentsMap.set(typeName, { file: component.file, symbol: componentSymbol });
+            }
+        }
+    }
+
+    addDiagnosticsForScopes(scopes: Scope[]) { //, changedFiles: BrsFile[]) {
+        const addDuplicateSymbolDiagnostics = true;
+        const missingSymbolInScope = new Map<BrsFile, Map<UnresolvedSymbol, Set<Scope>>>();
+        this.providedTreeMap.clear();
+        this.clearResolutionsForScopes(scopes);
+
+        // Check scope for duplicates and missing symbols
+        for (const scope of scopes) {
+            this.program.diagnostics.clearByFilter({
+                scope: scope,
+                tag: CrossScopeValidatorDiagnosticTag
+            });
+
+            const { missingSymbols, duplicatesMap } = this.getIssuesForScope(scope);
+            if (addDuplicateSymbolDiagnostics) {
+                for (const [_flag, dupeSet] of duplicatesMap.entries()) {
+                    if (dupeSet.size > 1) {
+
+                        const dupesArray = [...dupeSet.values()];
+
+                        for (let i = 0; i < dupesArray.length; i++) {
+                            const dupe = dupesArray[i];
+
+                            const dupeNode = dupe?.symbol?.data?.definingNode;
+                            if (!dupeNode) {
+                                continue;
+                            }
+                            let thisName = dupe.symbol?.name;
+                            const wrappingNameSpace = dupeNode?.findAncestor<NamespaceStatement>(isNamespaceStatement);
+
+                            if (wrappingNameSpace) {
+                                thisName = `${wrappingNameSpace.getName(ParseMode.BrighterScript)}.` + thisName;
+                            }
+
+                            const thisNodeKindName = util.getAstNodeFriendlyName(dupeNode) ?? 'Item';
+
+                            for (let j = 0; j < dupesArray.length; j++) {
+                                if (i === j) {
+                                    continue;
+                                }
+                                const otherDupe = dupesArray[j];
+                                if (!otherDupe || dupe.symbol === otherDupe.symbol) {
+                                    continue;
+                                }
+
+                                const otherDupeNode = otherDupe.symbol.data?.definingNode;
+                                const otherIsGlobal = otherDupe.file.srcPath === 'global';
+
+                                if (isFunctionStatement(dupeNode) && isFunctionStatement(otherDupeNode)) {
+                                    // duplicate functions are handled in ScopeValidator
+                                    continue;
+                                }
+                                if (otherIsGlobal &&
+                                    (isInterfaceStatement(dupeNode) ||
+                                        isEnumStatement(dupeNode) ||
+                                        isConstStatement(dupeNode))) {
+                                    // these are allowed to shadow global functions
+                                    continue;
+                                }
+                                let thatName = otherDupe.symbol?.name;
+
+                                if (otherDupeNode) {
+                                    const otherWrappingNameSpace = otherDupeNode?.findAncestor<NamespaceStatement>(isNamespaceStatement);
+                                    if (otherWrappingNameSpace) {
+                                        thatName = `${otherWrappingNameSpace.getName(ParseMode.BrighterScript)}.` + thatName;
+                                    }
+                                }
+
+                                const thatNodeKindName = otherIsGlobal ? 'Global Function' : util.getAstNodeFriendlyName(otherDupeNode) ?? 'Item';
+                                let thisNameRange = (dupeNode as any)?.tokens?.name?.range ?? dupeNode.range;
+                                let thatNameRange = (otherDupeNode as any)?.tokens?.name?.range ?? otherDupeNode?.range;
+
+                                const relatedInformation = thatNameRange ? [{
+                                    message: `${thatNodeKindName} declared here`,
+                                    location: util.createLocation(
+                                        URI.file(otherDupe.file?.srcPath).toString(),
+                                        thatNameRange
+                                    )
+                                }] : undefined;
+                                this.program.diagnostics.register({
+                                    ...DiagnosticMessages.nameCollision(thisNodeKindName, thatNodeKindName, thatName),
+                                    file: dupe.file,
+                                    range: thisNameRange,
+                                    relatedInformation: relatedInformation
+                                }, {
+                                    scope: scope,
+                                    tags: [CrossScopeValidatorDiagnosticTag]
+                                });
+                            }
+                        }
+                    }
+                }
+            }
+            // build map of the symbols and scopes where the symbols are missing per file
+            for (const missingSymbol of missingSymbols) {
+                let missingSymbolPerFile = missingSymbolInScope.get(missingSymbol.file);
+                if (!missingSymbolPerFile) {
+                    missingSymbolPerFile = new Map<UnresolvedSymbol, Set<Scope>>();
+                    missingSymbolInScope.set(missingSymbol.file, missingSymbolPerFile);
+                }
+                let scopesWithMissingSymbol = missingSymbolPerFile.get(missingSymbol);
+                if (!scopesWithMissingSymbol) {
+                    scopesWithMissingSymbol = new Set<Scope>();
+                    missingSymbolPerFile.set(missingSymbol, scopesWithMissingSymbol);
+                }
+                scopesWithMissingSymbol.add(scope);
+            }
+        }
+
+        // Check each file for missing symbols - if they are missing in SOME scopes, add diagnostic
+        for (let [file, missingSymbolPerFile] of missingSymbolInScope.entries()) {
+            for (const [symbol, scopeList] of missingSymbolPerFile) {
+                const typeChainResult = util.processTypeChain(symbol.typeChain);
+
+                for (const scope of scopeList) {
+                    this.program.diagnostics.register({
+
+                        ...this.getCannotFindDiagnostic(scope, typeChainResult),
+                        file: file,
+                        range: typeChainResult.range
+                    }, {
+                        scope: scope,
+                        tags: [CrossScopeValidatorDiagnosticTag]
+                    });
+                }
+            }
+        }
+
+        for (const resolution of this.getIncompatibleSymbolResolutions()) {
+            const symbol = resolution.symbol;
+            const incompatibleScopes = resolution.incompatibleScopes;
+            if (incompatibleScopes.size > 1) {
+                const typeChainResult = util.processTypeChain(symbol.typeChain);
+                const scopeListName = [...incompatibleScopes.values()].map(s => s.name).join(', ');
+                this.program.diagnostics.register({
+                    ...DiagnosticMessages.incompatibleSymbolDefinition(typeChainResult.fullChainName, scopeListName),
+                    file: symbol.file,
+                    range: typeChainResult.range
+                }, {
+                    tags: [CrossScopeValidatorDiagnosticTag]
+                });
+            }
+        }
+    }
+
+    getIncompatibleSymbolResolutions() {
+        const incompatibleResolutions = new Array<{ symbol: UnresolvedSymbol; incompatibleScopes: Set<Scope> }>();
+        // check all resolutions and check if there are resolutions that are not compatible across scopes
+        for (const [symbol, resolutionDetails] of this.resolutionsMap.entries()) {
+            if (resolutionDetails.size < 2) {
+                // there is only one resolution... no worries
+                continue;
+            }
+            const resolutionsList = [...resolutionDetails];
+            const prime = resolutionsList[0];
+            let incompatibleScopes = new Set<Scope>();
+            let addedPrime = false;
+            for (let i = 1; i < resolutionsList.length; i++) {
+                let providedSymbolType = prime.providedSymbol.type;
+                const symbolInThisScope = resolutionsList[i].providedSymbol;
+
+                //get more general type
+                if (providedSymbolType.isEqual(symbolInThisScope.type)) {
+                    //type in this scope is the same as one we're already checking
+                } else if (providedSymbolType.isTypeCompatible(symbolInThisScope.type)) {
+                    //type in this scope is compatible with one we're storing. use most generic
+                    providedSymbolType = symbolInThisScope.type;
+                } else if (symbolInThisScope.type.isTypeCompatible(providedSymbolType)) {
+                    // type we're storing is more generic that the type in this scope
+                } else {
+                    // type in this scope is not compatible with other types for this symbol
+                    if (!addedPrime) {
+                        incompatibleScopes.add(prime.scope);
+                        addedPrime = true;
+                    }
+                    incompatibleScopes.add(resolutionsList[i].scope);
+                }
+            }
+
+            if (incompatibleScopes.size > 1) {
+                incompatibleResolutions.push({
+                    symbol: symbol,
+                    incompatibleScopes: incompatibleScopes
+                });
+            }
+        }
+        return incompatibleResolutions;
+    }
+
+    private getCannotFindDiagnostic(scope: Scope, typeChainResult: TypeChainProcessResult) {
+        const parentDescriptor = this.getParentTypeDescriptor(this.getProvidedTree(scope)?.providedTree, typeChainResult);
+        if (isCallExpression(typeChainResult.astNode?.parent) && typeChainResult.astNode?.parent.callee === typeChainResult.astNode) {
+            return DiagnosticMessages.cannotFindFunction(typeChainResult.itemName, typeChainResult.fullNameOfItem, typeChainResult.itemParentTypeName, parentDescriptor);
+        }
+        return DiagnosticMessages.cannotFindName(typeChainResult.itemName, typeChainResult.fullNameOfItem, typeChainResult.itemParentTypeName, parentDescriptor);
+    }
+
+    private getParentTypeDescriptor(provided: ProvidedNode, typeChainResult: TypeChainProcessResult) {
+        if (typeChainResult.itemParentTypeKind === BscTypeKind.NamespaceType || provided?.getNamespace(typeChainResult.itemParentTypeName)) {
+            return 'namespace';
+        }
+        return 'type';
+    }
+
+}
diff --git a/src/DiagnosticManager.ts b/src/DiagnosticManager.ts
index b60da0ee..73185732 100644
--- a/src/DiagnosticManager.ts
+++ b/src/DiagnosticManager.ts
@@ -98,28 +98,32 @@ export class DiagnosticManager {
         for (const cachedDiagnostic of this.diagnosticsCache.values()) {
             const diagnostic = { ...cachedDiagnostic.diagnostic };
             const relatedInformation = [...cachedDiagnostic.diagnostic.relatedInformation];
+            const affectedScopes = new Set<Scope>();
             for (const context of cachedDiagnostic.contexts.values()) {
                 if (context.scope) {
-                    const scope = context.scope;
-                    if (isXmlScope(scope) && scope.xmlFile?.srcPath) {
-                        relatedInformation.push({
-                            message: `In component scope '${scope?.xmlFile?.componentName?.text}'`,
-                            location: util.createLocation(
-                                URI.file(scope.xmlFile.srcPath).toString(),
-                                scope?.xmlFile?.ast?.componentElement?.getAttribute('name')?.tokens?.value?.range ?? util.createRange(0, 0, 0, 10)
-                            )
-                        });
-                    } else {
-                        relatedInformation.push({
-                            message: `In scope '${scope.name}'`,
-                            location: util.createLocation(
-                                URI.file(diagnostic.file.srcPath).toString(),
-                                diagnostic.range
-                            )
-                        });
-                    }
+                    affectedScopes.add(context.scope);
                 }
             }
+            for (const scope of affectedScopes) {
+                if (isXmlScope(scope) && scope.xmlFile?.srcPath) {
+                    relatedInformation.push({
+                        message: `In component scope '${scope?.xmlFile?.componentName?.text}'`,
+                        location: util.createLocation(
+                            URI.file(scope.xmlFile.srcPath).toString(),
+                            scope?.xmlFile?.ast?.componentElement?.getAttribute('name')?.tokens?.value?.range ?? util.createRange(0, 0, 0, 10)
+                        )
+                    });
+                } else {
+                    relatedInformation.push({
+                        message: `In scope '${scope.name}'`,
+                        location: util.createLocation(
+                            URI.file(diagnostic.file.srcPath).toString(),
+                            diagnostic.range
+                        )
+                    });
+                }
+
+            }
             diagnostic.relatedInformation = relatedInformation;
             results.push(diagnostic);
         }
diff --git a/src/DiagnosticMessages.ts b/src/DiagnosticMessages.ts
index d4dd3073..2f6f4994 100644
--- a/src/DiagnosticMessages.ts
+++ b/src/DiagnosticMessages.ts
@@ -607,7 +607,7 @@ export let DiagnosticMessages = {
         code: 1115,
         severity: DiagnosticSeverity.Error
     }),
-    functionCannotHaveSameNameAsClass: (className: string) => ({
+    __unused6: (className: string) => ({
         message: `Function has same name as class '${className}'`,
         code: 1116,
         severity: DiagnosticSeverity.Error
@@ -771,7 +771,7 @@ export let DiagnosticMessages = {
         severity: DiagnosticSeverity.Error
     }),
     incompatibleSymbolDefinition: (symbol: string, scopeName: string) => ({
-        message: `'${symbol}' is incompatible across scope group (${scopeName})`, // TODO: Add scopes where it was defined
+        message: `'${symbol}' is incompatible across these scopes: ${scopeName}`,
         code: 1145,
         severity: DiagnosticSeverity.Error
     }),
diff --git a/src/Program.ts b/src/Program.ts
index c7710ea1..455f163d 100644
--- a/src/Program.ts
+++ b/src/Program.ts
@@ -42,13 +42,14 @@ import { rokuDeploy } from 'roku-deploy';
 import type { SGNodeData, BRSComponentData, BRSEventData, BRSInterfaceData } from './roku-types';
 import { nodes, components, interfaces, events } from './roku-types';
 import { ComponentType } from './types/ComponentType';
-import type { BscType } from './types/BscType';
 import { InterfaceType } from './types/InterfaceType';
 import { BuiltInInterfaceAdder } from './types/BuiltInInterfaceAdder';
 import type { UnresolvedSymbol } from './AstValidationSegmenter';
 import { WalkMode, createVisitor } from './astUtils/visitors';
 import type { BscFile } from './files/BscFile';
 import { Stopwatch } from './Stopwatch';
+import { firstBy } from 'thenby';
+import { CrossScopeValidator } from './CrossScopeValidator';
 import { DiagnosticManager } from './DiagnosticManager';
 import { ProgramValidatorDiagnosticsTag } from './bscPlugin/validation/ProgramValidator';
 import type { ProvidedSymbolInfo, BrsFile } from './files/BrsFile';
@@ -63,12 +64,6 @@ export interface SignatureInfoObj {
     signature: SignatureInformation;
 }
 
-export interface ProgramValidationInfo {
-    symbolsNotDefinedInEveryScope: { symbol: UnresolvedSymbol; scope: Scope }[];
-    duplicateSymbolsInSameScope: { symbol: UnresolvedSymbol; scope: Scope }[];
-    symbolsNotConsistentAcrossScopes: { symbol: UnresolvedSymbol; scopes: Scope[] }[];
-}
-
 export class Program {
     constructor(
         /**
@@ -312,6 +307,22 @@ export class Program {
         }
     }
 
+    /**
+     * Get the sorted names of custom components
+     */
+    public getSortedComponentNames() {
+        const componentNames = Object.keys(this.components);
+        componentNames.sort((a, b) => {
+            if (a < b) {
+                return -1;
+            } else if (b < a) {
+                return 1;
+            }
+            return 0;
+        });
+        return componentNames;
+    }
+
     /**
      * Keeps a set of all the components that need to have their types updated during the current validation cycle
      */
@@ -772,9 +783,12 @@ export class Program {
             //if this is a pkg:/source file, notify the `source` scope that it has changed
             if (this.isSourceBrsFile(file)) {
                 this.dependencyGraph.removeDependency('scope:source', file.dependencyGraphKey);
+            }
+            if (isBrsFile(file)) {
                 if (!keepSymbolInformation) {
                     this.fileSymbolInformation.delete(file.pkgPath);
                 }
+                this.crossScopeValidation.clearResolutionsForFile(file);
             }
 
             //if this is a component, remove it from our components map
@@ -792,7 +806,8 @@ export class Program {
         }
     }
 
-    public lastValidationInfo = new Map<string, ProgramValidationInfo>();
+    //public lastValidationInfo = new Map<string, ProgramValidationInfo>();
+    public crossScopeValidation = new CrossScopeValidator(this);
 
     private isFirstValidation = true;
 
@@ -809,10 +824,10 @@ export class Program {
             this.plugins.emit('onProgramValidate', programValidateEvent);
 
             const metrics = {
+                filesChanged: 0,
                 filesValidated: 0,
                 fileValidationTime: '',
-                fileInfoGenerationTime: '',
-                programValidationTime: '',
+                crossScopeValidationTime: '',
                 scopesValidated: 0,
                 totalLinkTime: '',
                 totalScopeValidationTime: '',
@@ -822,10 +837,12 @@ export class Program {
             const validationStopwatch = new Stopwatch();
             //validate every file
             const brsFilesValidated: BrsFile[] = [];
-            const afterValidateFiles = [];
+            const afterValidateFiles: BscFile[] = [];
 
             metrics.fileValidationTime = validationStopwatch.getDurationTextFor(() => {
-                for (const file of Object.values(this.files)) {
+                //sort files by path so we get consistent results
+                const files = Object.values(this.files).sort(firstBy(x => x.srcPath));
+                for (const file of files) {
                     //for every unvalidated file, validate it
                     if (!file.isValidated) {
                         const validateFileEvent = {
@@ -852,22 +869,7 @@ export class Program {
                 }
             }).durationText;
 
-            metrics.filesValidated = afterValidateFiles.length;
-
-            metrics.fileInfoGenerationTime = validationStopwatch.getDurationTextFor(() => {
-                // build list of all changed symbols in each file that changed
-                this.updateLastValidationFileInfo(brsFilesValidated);
-            }).durationText;
-
-            validationStopwatch.stop();
-            metrics.fileInfoGenerationTime = validationStopwatch.getDurationText();
-            validationStopwatch.reset();
-            validationStopwatch.start();
-
-            metrics.programValidationTime = validationStopwatch.getDurationTextFor(() => {
-                this.detectIncompatibleSymbolsAcrossScopes();
-            }).durationText;
-
+            metrics.filesChanged = afterValidateFiles.length;
 
             // Build component types for any component that changes
             this.logger.time(LogLevel.info, ['Build component types'], () => {
@@ -890,14 +892,41 @@ export class Program {
                 const changedSymbolsSetArr = changedSymbolsMapArr.map(symMap => symMap.get(flag));
                 changedSymbols.set(flag, new Set(...changedSymbolsSetArr));
             }
+
+            const filesToBeValidatedInScopeContext = new Set<BscFile>(afterValidateFiles);
+
+            metrics.crossScopeValidationTime = validationStopwatch.getDurationTextFor(() => {
+                const scopesToCheck = this.getScopesForCrossScopeValidation();
+                this.crossScopeValidation.buildComponentsMap();
+                this.crossScopeValidation.addDiagnosticsForScopes(scopesToCheck);
+                const filesToRevalidate = this.crossScopeValidation.getFilesRequiringChangedSymbol(scopesToCheck, changedSymbols);
+                for (const file of filesToRevalidate) {
+                    filesToBeValidatedInScopeContext.add(file);
+                }
+            }).durationText;
+
+            metrics.filesValidated = filesToBeValidatedInScopeContext.size;
+
             let linkTime = 0;
             let validationTime = 0;
             let scopesValidated = 0;
+            let changedFiles = new Set<BscFile>(afterValidateFiles);
             this.logger.time(LogLevel.info, ['Validate all scopes'], () => {
-
-                for (let scopeName in this.scopes) {
+                //sort the scope names so we get consistent results
+                const scopeNames = this.getSortedScopeNames();
+                for (const file of filesToBeValidatedInScopeContext) {
+                    if (isBrsFile(file)) {
+                        file.validationSegmenter.unValidateAllSegments();
+                    }
+                }
+                for (let scopeName of scopeNames) {
                     let scope = this.scopes[scopeName];
-                    const scopeValidated = scope.validate({ changedFiles: afterValidateFiles, changedSymbols: changedSymbols, initialValidation: this.isFirstValidation });
+                    const scopeValidated = scope.validate({
+                        filesToBeValidatedInScopeContext: filesToBeValidatedInScopeContext,
+                        changedSymbols: changedSymbols,
+                        changedFiles: changedFiles,
+                        initialValidation: this.isFirstValidation
+                    });
                     if (scopeValidated) {
                         scopesValidated++;
                     }
@@ -924,76 +953,6 @@ export class Program {
         });
     }
 
-    private updateLastValidationFileInfo(brsFilesValidated: BrsFile[]) {
-        this.lastValidationInfo.clear();
-        for (const file of brsFilesValidated) {
-
-            const fileInfo: {
-                symbolsNotDefinedInEveryScope: { symbol: UnresolvedSymbol; scope: Scope }[];
-                duplicateSymbolsInSameScope: { symbol: UnresolvedSymbol; scope: Scope }[];
-                symbolsNotConsistentAcrossScopes: { symbol: UnresolvedSymbol; scopes: Scope[] }[];
-            } = {
-                symbolsNotDefinedInEveryScope: [],
-                duplicateSymbolsInSameScope: [],
-                symbolsNotConsistentAcrossScopes: []
-            };
-            const scopesToCheckForConsistency = this.getScopesForFile(file);
-            for (const symbol of file.requiredSymbols) {
-                let providedSymbolType: BscType;
-                let scopesDefiningSymbol: Scope[] = [];
-                let scopesAreInconsistent = false;
-
-                for (const scope of scopesToCheckForConsistency) {
-                    let symbolFoundInScope = false;
-                    for (const scopeFile of scope.getAllFiles()) {
-                        if (!isBrsFile(scopeFile) || scopeFile.isTypedef || scopeFile.hasTypedef) {
-                            continue;
-                        }
-                        const lowerFirstSymbolName = symbol.typeChain?.[0]?.name.toLowerCase();
-                        let symbolInThisScope = scopeFile.providedSymbols.symbolMap?.get(symbol.flags)?.get(lowerFirstSymbolName);
-                        if (!symbolInThisScope && symbol.containingNamespaces?.length > 0) {
-                            const fullNameWithNamespaces = (symbol.containingNamespaces.join('.') + '.' + lowerFirstSymbolName).toLowerCase();
-                            symbolInThisScope = scopeFile.providedSymbols.symbolMap?.get(symbol.flags)?.get(fullNameWithNamespaces);
-                        }
-                        if (symbolInThisScope) {
-                            if (symbolFoundInScope) {
-                                // this is duplicately defined!
-                                fileInfo.duplicateSymbolsInSameScope.push({ symbol: symbol, scope: scope });
-                            } else {
-                                symbolFoundInScope = true;
-                                scopesDefiningSymbol.push(scope);
-                                //check for consistency across scopes
-                                if (!providedSymbolType) {
-                                    providedSymbolType = symbolInThisScope.type;
-                                } else {
-                                    //get more general type
-                                    if (providedSymbolType.isEqual(symbolInThisScope.type)) {
-                                        //type in this scope is the same as one we're already checking
-                                    } else if (providedSymbolType.isTypeCompatible(symbolInThisScope.type)) {
-                                        //type in this scope is compatible with one we're storing. use most generic
-                                        providedSymbolType = symbolInThisScope.type;
-                                    } else if (symbolInThisScope.type.isTypeCompatible(providedSymbolType)) {
-                                        // type we're storing is more generic that the type in this scope
-                                    } else {
-                                        // type in this scope is not compatible with other types for this symbol
-                                        scopesAreInconsistent = true;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    if (!symbolFoundInScope) {
-                        fileInfo.symbolsNotDefinedInEveryScope.push({ symbol: symbol, scope: scope });
-                    }
-                }
-                if (scopesAreInconsistent) {
-                    fileInfo.symbolsNotConsistentAcrossScopes.push({ symbol: symbol, scopes: scopesDefiningSymbol });
-                }
-            }
-            this.lastValidationInfo.set(file.srcPath.toLowerCase(), fileInfo);
-        }
-    }
-
     // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
     private logValidationMetrics(metrics: { [key: string]: number | string }) {
         let logs = [] as string[];
@@ -1003,20 +962,15 @@ export class Program {
         this.logger.info(`Validation Metrics: ${logs.join(', ')}`);
     }
 
-
-    private detectIncompatibleSymbolsAcrossScopes() {
-        for (const [lowerFilePath, fileInfo] of this.lastValidationInfo.entries()) {
-            const file = this.files[lowerFilePath];
-            for (const symbolAndScopes of fileInfo.symbolsNotConsistentAcrossScopes) {
-                const typeChainResult = util.processTypeChain(symbolAndScopes.symbol.typeChain);
-                const scopeListName = symbolAndScopes.scopes.map(s => s.name).join(', ');
-                this.diagnostics.register({
-                    ...DiagnosticMessages.incompatibleSymbolDefinition(typeChainResult.fullNameOfItem, scopeListName),
-                    file: file,
-                    range: typeChainResult.range
-                }, { tags: [ProgramValidatorDiagnosticsTag] });
+    private getScopesForCrossScopeValidation() {
+        const scopesForCrossScopeValidation = [];
+        for (let scopeName of this.getSortedScopeNames()) {
+            let scope = this.scopes[scopeName];
+            if (this.globalScope !== scope && !scope.isValidated) {
+                scopesForCrossScopeValidation.push(scope);
             }
         }
+        return scopesForCrossScopeValidation;
     }
 
     /**
@@ -1092,6 +1046,30 @@ export class Program {
         }
     }
 
+    /**
+     * Gets a sorted list of all scopeNames, always beginning with "global", "source", then any others in alphabetical order
+     */
+    private getSortedScopeNames() {
+        return Object.keys(this.scopes).sort((a, b) => {
+            if (a === 'global') {
+                return -1;
+            } else if (b === 'global') {
+                return 1;
+            }
+            if (a === 'source') {
+                return -1;
+            } else if (b === 'source') {
+                return 1;
+            }
+            if (a < b) {
+                return -1;
+            } else if (b < a) {
+                return 1;
+            }
+            return 0;
+        });
+    }
+
     /**
      * Get a list of all scopes the file is loaded into
      * @param file the file
@@ -1101,7 +1079,8 @@ export class Program {
 
         let result = [] as Scope[];
         if (resolvedFile) {
-            for (let key in this.scopes) {
+            const scopeKeys = this.getSortedScopeNames();
+            for (let key of scopeKeys) {
                 let scope = this.scopes[key];
 
                 if (scope.hasFile(resolvedFile)) {
@@ -1116,7 +1095,8 @@ export class Program {
      * Get the first found scope for a file.
      */
     public getFirstScopeForFile(file: BscFile): Scope | undefined {
-        for (let key in this.scopes) {
+        const scopeKeys = this.getSortedScopeNames();
+        for (let key of scopeKeys) {
             let scope = this.scopes[key];
 
             if (scope.hasFile(file)) {
diff --git a/src/Scope.spec.ts b/src/Scope.spec.ts
index eeec6940..faa4d2e6 100644
--- a/src/Scope.spec.ts
+++ b/src/Scope.spec.ts
@@ -25,7 +25,6 @@ import { ArrayType } from './types/ArrayType';
 import { AssociativeArrayType } from './types/AssociativeArrayType';
 import { InterfaceType } from './types/InterfaceType';
 import { ComponentType } from './types/ComponentType';
-import * as path from 'path';
 import { WalkMode, createVisitor } from './astUtils/visitors';
 import type { FunctionExpression } from './parser/Expression';
 import { ObjectType } from './types';
@@ -141,7 +140,8 @@ describe('Scope', () => {
         `);
         program.validate();
         expectDiagnostics(program, [
-            DiagnosticMessages.nameCollision('Const', 'Function', 'options').message
+            DiagnosticMessages.nameCollision('Const', 'Function', 'alpha.options').message,
+            DiagnosticMessages.nameCollision('Function', 'Const', 'alpha.options').message
         ]);
     });
 
@@ -370,7 +370,7 @@ describe('Scope', () => {
             program.validate();
             expectDiagnostics(program, [
                 {
-                    message: DiagnosticMessages.cannotFindName('delta', 'constants.alpha.delta', 'constants.alpha', 'namespace').message,
+                    ...DiagnosticMessages.cannotFindName('delta', 'constants.alpha.delta', 'constants.alpha', 'namespace'),
                     file: {
                         srcPath: buttonPrimary.srcPath
                     },
@@ -378,7 +378,7 @@ describe('Scope', () => {
                         message: `In component scope 'ButtonPrimary'`
                     }]
                 }, {
-                    message: DiagnosticMessages.cannotFindName('delta', 'constants.alpha.delta', 'constants.alpha', 'namespace').message,
+                    ...DiagnosticMessages.cannotFindName('delta', 'constants.alpha.delta', 'constants.alpha', 'namespace'),
                     file: {
                         srcPath: buttonSecondary.srcPath
                     },
@@ -1018,10 +1018,12 @@ describe('Scope', () => {
                     end function
                 `);
                 program.validate();
-                expectDiagnostics(program, [{
-                    message: DiagnosticMessages.scopeFunctionShadowedByBuiltInFunction().message,
-                    range: Range.create(4, 29, 4, 32)
-                }]);
+                expectDiagnostics(program, [
+                    DiagnosticMessages.nameCollision('Function', 'Global Function', 'Str').message,
+                    {
+                        message: DiagnosticMessages.scopeFunctionShadowedByBuiltInFunction().message,
+                        range: Range.create(4, 29, 4, 32)
+                    }]);
             });
         });
 
@@ -1445,7 +1447,7 @@ describe('Scope', () => {
                 program.validate();
                 let diagnostics = program.getDiagnostics();
                 expectDiagnosticsIncludes(diagnostics, [
-                    DiagnosticMessages.nameCollision('Const', 'Function', 'MY_CONST').message
+                    DiagnosticMessages.nameCollision('Const', 'Function', 'SomeEnum.MY_CONST').message
                 ]);
             });
         });
@@ -2602,7 +2604,7 @@ describe('Scope', () => {
             expect(mainFnScope).to.exist;
             const chimpType = mainFnScope.symbolTable.getSymbol('chimp', SymbolTypeFlag.runtime)[0].type;
             expectTypeToBe(chimpType, ClassType);
-            expectTypeToBe((chimpType as ClassType).superClass, ClassType);
+            expectTypeToBe((chimpType as ClassType).parentType, ClassType);
             expectTypeToBe(mainFnScope.symbolTable.getSymbol('chimpHasLegs', SymbolTypeFlag.runtime)[0].type, BooleanType);
             expectTypeToBe(mainFnScope.symbolTable.getSymbol('chimpSpeed', SymbolTypeFlag.runtime)[0].type, IntegerType);
             expectTypeToBe(mainFnScope.symbolTable.getSymbol('fidoSpeed', SymbolTypeFlag.runtime)[0].type, IntegerType);
@@ -3698,203 +3700,6 @@ describe('Scope', () => {
 
     describe('provides & requires', () => {
 
-        it('finds a required symbol in another file', () => {
-            let file1 = program.setFile<BrsFile>('source/file1.bs', `
-                sub callOutsideFunc()
-                    outsideFunc()
-                end sub
-            `);
-            let file2 = program.setFile<BrsFile>('source/file2.bs', `
-                sub outsideFunc()
-                    print "hello"
-                end sub
-            `);
-            program.validate();
-            expectZeroDiagnostics(program);
-            expect(file1.requiredSymbols.length).to.eq(1);
-            expect(file2.requiredSymbols.length).to.eq(0);
-            let file1Info = program.lastValidationInfo.get(file1.srcPath.toLowerCase());
-            expect(file1Info.symbolsNotDefinedInEveryScope.length).to.eq(0);
-            expect(file1Info.symbolsNotConsistentAcrossScopes.length).to.eq(0);
-        });
-
-        it('finds a required symbol in another file for each scope', () => {
-            let file1 = program.setFile<BrsFile>('source/file1.bs', `
-                sub callOutsideFunc()
-                    outsideFunc()
-                end sub
-            `);
-            let file2 = program.setFile<BrsFile>('source/file2.bs', `
-                sub outsideFunc()
-                    print "hello from source"
-                end sub
-            `);
-
-            program.setFile<BrsFile>('components/Widget.xml', trim`
-                <?xml version="1.0" encoding="utf-8" ?>
-                <component name="Widget" extends="Group">
-                    <script uri="Widget.bs"/>
-                    <script uri="pkg:/source/file1.bs"/>
-                </component>
-            `);
-            let widgetBs = program.setFile<BrsFile>('components/Widget.bs', `
-                sub init()
-                    callOutsideFunc()
-                end sub
-
-                sub outsideFunc()
-                    print "hello from widget"
-                end sub
-            `);
-            program.validate();
-            expectZeroDiagnostics(program);
-            expect(file1.requiredSymbols.length).to.eq(1);
-            expect(file2.requiredSymbols.length).to.eq(0);
-            expect(widgetBs.requiredSymbols.length).to.eq(1);
-            let file1Info = program.lastValidationInfo.get(file1.srcPath.toLowerCase());
-            expect(file1Info.symbolsNotDefinedInEveryScope.length).to.eq(0);
-            expect(file1Info.symbolsNotConsistentAcrossScopes.length).to.eq(0);
-        });
-
-        it('finds a required symbol in a namespace in another file', () => {
-            let file1 = program.setFile<BrsFile>('source/file1.bs', `
-                namespace alpha
-                    sub callOutsideFunc()
-                        outsideFunc()
-                    end sub
-                end namespace
-            `);
-            let file2 = program.setFile<BrsFile>('source/file2.bs', `
-                namespace alpha
-                    sub outsideFunc()
-                        print "hello from source"
-                    end sub
-                end namespace
-            `);
-
-            program.validate();
-            expectZeroDiagnostics(program);
-            expect(file1.requiredSymbols.length).to.eq(1);
-            expect(file2.requiredSymbols.length).to.eq(0);
-            let file1Info = program.lastValidationInfo.get(file1.srcPath.toLowerCase());
-            expect(file1Info.symbolsNotDefinedInEveryScope.length).to.eq(0);
-            expect(file1Info.symbolsNotConsistentAcrossScopes.length).to.eq(0);
-        });
-
-
-        it('finds a if a required symbol is defined different in different scopes', () => {
-            let file1 = program.setFile<BrsFile>('source/file1.bs', `
-                sub callOutsideFunc()
-                    print outsideFunc()
-                end sub
-            `);
-            let file2 = program.setFile<BrsFile>('source/file2.bs', `
-                function outsideFunc() as string
-                    return "hello from source"
-                end function
-            `);
-
-            program.setFile<BrsFile>('components/Widget.xml', trim`
-                <?xml version="1.0" encoding="utf-8" ?>
-                <component name="Widget" extends="Group">
-                    <script uri="Widget.bs"/>
-                    <script uri="pkg:/source/file1.bs"/>
-                </component>
-            `);
-            let widgetBs = program.setFile<BrsFile>('components/Widget.bs', `
-                sub init()
-                    print callOutsideFunc()
-                end sub
-
-                function outsideFunc() as integer
-                    return 123
-                end function
-            `);
-            program.validate();
-            //expectZeroDiagnostics(program);
-            expect(file1.requiredSymbols.length).to.eq(1);
-            expect(file2.requiredSymbols.length).to.eq(0);
-            expect(widgetBs.requiredSymbols.length).to.eq(1);
-            let file1Info = program.lastValidationInfo.get(file1.srcPath.toLowerCase());
-            expect(file1Info.symbolsNotDefinedInEveryScope.length).to.eq(0);
-            expect(file1Info.symbolsNotConsistentAcrossScopes.length).to.eq(1);
-            expect(file1Info.symbolsNotConsistentAcrossScopes[0].symbol.typeChain[0].name).to.eq('outsideFunc');
-        });
-
-        it('finds types defined in different file', () => {
-            let file1 = program.setFile<BrsFile>('source/file1.bs', `
-                function takesIface(z as MyInterface) as string
-                    return z.name
-                end function
-            `);
-            program.setFile<BrsFile>('source/file2.bs', `
-                interface MyInterface
-                    name as string
-                end interface
-            `);
-            program.validate();
-            expectZeroDiagnostics(program);
-            expect(file1.requiredSymbols.length).to.eq(1);
-            expect(file1.requiredSymbols[0].flags).to.eq(SymbolTypeFlag.typetime);
-            expect(file1.requiredSymbols[0].typeChain[0].name).to.eq('MyInterface');
-            let file1Info = program.lastValidationInfo.get(file1.srcPath.toLowerCase());
-            expect(file1Info.symbolsNotDefinedInEveryScope.length).to.eq(0);
-        });
-
-        it('finds members of typecasts of types defined in different file', () => {
-            let file1 = program.setFile<BrsFile>('source/file1.bs', `
-                function takesIface(z) as string
-                    return (z as MyInterface).name
-                end function
-            `);
-            program.setFile<BrsFile>('source/file2.bs', `
-                interface MyInterface
-                    name as string
-                end interface
-            `);
-            program.validate();
-            expectZeroDiagnostics(program);
-            expect(file1.requiredSymbols.length).to.eq(1);
-            expect(file1.requiredSymbols[0].flags).to.eq(SymbolTypeFlag.typetime);
-            expect(file1.requiredSymbols[0].typeChain[0].name).to.eq('MyInterface');
-            let file1Info = program.lastValidationInfo.get(file1.srcPath.toLowerCase());
-            expect(file1Info.symbolsNotDefinedInEveryScope.length).to.eq(0);
-        });
-
-        it('finds symbols inconsistent across scopes', () => {
-            program.setFile<BrsFile>('source/file1.bs', `
-                function callsOther() as string
-                    return otherFunc()
-                end function
-            `);
-            program.setFile<BrsFile>('source/file2.bs', `
-                function otherFunc() as string
-                    return "hello"
-                end function
-            `);
-
-            program.setFile<BrsFile>('components/Widget.xml', trim`
-                <?xml version="1.0" encoding="utf-8" ?>
-                <component name="Widget" extends="Group">
-                    <script uri="Widget.bs"/>
-                    <script uri="pkg:/source/file1.bs"/>
-                </component>
-            `);
-            program.setFile<BrsFile>('components/Widget.bs', `
-                sub init()
-                    callsOther()
-                end sub
-
-                function otherFunc() as integer
-                    return 42
-                end function
-            `);
-            program.validate();
-            expectDiagnosticsIncludes(program, [
-                DiagnosticMessages.incompatibleSymbolDefinition('otherFunc', `source, components${path.sep}Widget.xml`).message
-            ]);
-        });
-
         it('a class can reference itself', () => {
             program.setFile<BrsFile>('source/klass.bs', `
                 class Klass
@@ -3954,7 +3759,7 @@ describe('Scope', () => {
             expect(file2.requiredSymbols.length).to.eq(0);
             expect(file2.providedSymbols.symbolMap.get(SymbolTypeFlag.typetime).size).to.eq(1);
             const file2TypeProvides = file2.providedSymbols.symbolMap.get(SymbolTypeFlag.typetime);
-            expectTypeToBe(file2TypeProvides.get('alpha.beta.charlie.someenum').type, EnumType);
+            expectTypeToBe(file2TypeProvides.get('alpha.beta.charlie.someenum').symbol.type, EnumType);
         });
 
         it('classes that extend classes in other files show change properly', () => {
@@ -4159,11 +3964,11 @@ describe('Scope', () => {
                 program.validate();
                 expectZeroDiagnostics(program);
                 expect(file1.requiredSymbols.length).to.eq(0);
-                const validationSegments = file1.getValidationSegments(file1.providedSymbols.changes);
+                const validationSegments = file1.validationSegmenter.getSegmentsWithChangedSymbols(file1.providedSymbols.changes);
                 expect(validationSegments).to.not.undefined;
             });
 
-            it('does not require symbols found in namespace of import', () => {
+            it('does require symbols found in namespace of import', () => {
                 let file1 = program.setFile<BrsFile>('source/file1.bs', `
                     import "pkg:/source/file2.bs"
 
@@ -4203,8 +4008,8 @@ describe('Scope', () => {
                 `);
                 program.validate();
                 expectZeroDiagnostics(program);
-                expect(file1.requiredSymbols.length).to.eq(0);
-                const validationSegments = file1.getValidationSegments(file1.providedSymbols.changes);
+                expect(file1.requiredSymbols.length).to.eq(2);
+                const validationSegments = file1.validationSegmenter.getSegmentsWithChangedSymbols(file1.providedSymbols.changes);
                 expect(validationSegments).to.not.undefined;
             });
         });
@@ -4283,8 +4088,8 @@ describe('Scope', () => {
             `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.nameCollision('Enum', 'Const', 'SomeName').message,
-                DiagnosticMessages.nameCollision('Const', 'Enum', 'SomeName').message
+                DiagnosticMessages.nameCollision('Const', 'Enum', 'SomeName').message,
+                DiagnosticMessages.nameCollision('Enum', 'Const', 'SomeName').message
             ]);
         });
 
@@ -4390,7 +4195,7 @@ describe('Scope', () => {
             `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.nameCollision('Class', 'Global Function', 'log')
+                DiagnosticMessages.nameCollision('Class', 'Global Function', 'Log')
             ]);
         });
 
@@ -4406,8 +4211,8 @@ describe('Scope', () => {
             `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.functionCannotHaveSameNameAsClass('someName'),
-                DiagnosticMessages.nameCollision('Class', 'Function', 'SomeName')
+                DiagnosticMessages.nameCollision('Class', 'Function', 'someName'),
+                DiagnosticMessages.nameCollision('Function', 'Class', 'SomeName')
             ]);
         });
 
@@ -4484,7 +4289,9 @@ describe('Scope', () => {
 
         // eslint-disable-next-line func-names, prefer-arrow-callback
         it.skip('namespace linking performance', function () {
-            this.timeout(30000); // this test takes a long time!
+            this.timeout(10000);
+            program.logger.logLevel = 'info';
+            // this test takes a few seconds (~4) it is skipped just to make regular test running faster
             program.options.autoImportComponentScript = true;
             const constFileContents = `
                 import "pkg:/source/consts2.bs"
@@ -4512,16 +4319,12 @@ describe('Scope', () => {
             `;
 
             const const2FileContents = `
-               import "pkg:/source/consts3.bs"
-
+                import "pkg:/source/consts3.bs"
+                import "pkg:/source/consts2a.bs"
                 namespace a.b.c
                     const ROOT2 = 1.41
                 end namespace
 
-                namespace d.e.f
-                    const GOLDEN = 1.62
-                end namespace
-
                 namespace a.b.c.d
                     const D = "D"
                 end namespace
@@ -4531,6 +4334,12 @@ describe('Scope', () => {
                 end namespace
             `;
 
+            const const2AFileContents = `
+                namespace d.e.f
+                    const GOLDEN = 1.62
+                end namespace
+            `;
+
             let const3FileContents = '';
             const exponentBase = 7; // 7^4 = 2401 total namespaces
             for (let i = 0; i < exponentBase; i++) {
@@ -4552,6 +4361,7 @@ describe('Scope', () => {
 
             program.setFile('source/consts.bs', constFileContents);
             program.setFile('source/consts2.bs', const2FileContents);
+            program.setFile('source/consts2a.bs', const2AFileContents);
             program.setFile('source/consts3.bs', const3FileContents);
 
             const widgetBsFileContents = `
@@ -4560,6 +4370,7 @@ describe('Scope', () => {
                     sub init()
                         print a.b.c.PI + d.e.f.EULER
                         print a.b.c.d.D + n.o.p.C
+                        print d.e.f.GOLDEN
                         print d.e.f.GOLDEN_EULER
                         print alpha_0.beta_0.charlie_0.delta_0.getNum().toStr() +  alpha_0.beta_0.charlie_0.delta_0.TEST
                     end sub
@@ -4581,7 +4392,13 @@ describe('Scope', () => {
             program.validate();
             program.setFile('source/consts3.bs', const3FileContents);
             program.validate();
+            program.setFile('source/consts2a.bs', `
+                namespace d.e.f
+                    const NOT_GOLDEN = 1.62
+                end namespace
+            `);
+            program.validate();
+
         });
     });
-
 });
diff --git a/src/Scope.ts b/src/Scope.ts
index b8e80430..dedc1da6 100644
--- a/src/Scope.ts
+++ b/src/Scope.ts
@@ -76,6 +76,7 @@ export class Scope {
         return this.cache.getOrAdd('namespaceLookup', () => this.buildNamespaceLookup());
     }
 
+
     /**
      * Get a NamespaceContainer by its name, looking for a fully qualified version first, then global version next if not found
      */
@@ -765,30 +766,24 @@ export class Scope {
     };
 
     public validate(validationOptions: ScopeValidationOptions = { force: false }) {
+        this.validationMetrics = {
+            linkTime: 0,
+            validationTime: 0
+        };
+
         //if this scope is already validated, no need to revalidate
         if (this.isValidated === true && !validationOptions.force) {
             this.logDebug('validate(): already validated');
             return false;
         }
 
-        const hasChangedSymbols = validationOptions.changedSymbols?.get(SymbolTypeFlag.runtime).size > 0 || validationOptions.changedSymbols?.get(SymbolTypeFlag.typetime).size > 0;
-        let immediateFileChanged = false;
-        if (!validationOptions.initialValidation && validationOptions.changedSymbols && !hasChangedSymbols && validationOptions.changedFiles) {
-            for (let file of this.getImmediateFiles()) {
-                if (validationOptions.changedFiles.includes(file)) {
-                    immediateFileChanged = true;
-                    break;
-                }
-            }
-
-            if (!immediateFileChanged) {
-                // There was no need to validate this scope.
-                (this as any).isValidated = true;
-                return false;
-            }
+        if (!validationOptions.initialValidation && validationOptions.filesToBeValidatedInScopeContext?.size === 0) {
+            // There was no need to validate this scope.
+            (this as any).isValidated = true;
+            return false;
         }
 
-        this.useFileCachesForFileLinkLookups = !validationOptions.initialValidation;
+        this.useFileCachesForFileLinkLookups = true;//!validationOptions.initialValidation;
 
         this.program.logger.time(LogLevel.debug, [this._debugLogComponentName, 'validate()'], () => {
 
@@ -799,8 +794,6 @@ export class Scope {
                 this.logDebug('validate(): validating parent first');
                 parentScope.validate(validationOptions);
             }
-            //clear the scope's errors list (we will populate them from this method)
-
 
             //Since statements from files are shared across multiple scopes, we need to link those statements to the current scope
 
@@ -810,7 +803,7 @@ export class Scope {
             const scopeValidateEvent = {
                 program: this.program,
                 scope: this,
-                changedFiles: validationOptions?.changedFiles,
+                changedFiles: new Array<BscFile>(...(validationOptions?.changedFiles?.values() ?? [])),
                 changedSymbols: validationOptions?.changedSymbols
             };
             t0 = performance.now();
@@ -822,6 +815,9 @@ export class Scope {
             this.unlinkSymbolTable();
             (this as any).isValidated = true;
         });
+        for (let file of this.getAllFiles()) {
+            validationOptions.filesToBeValidatedInScopeContext?.delete(file);
+        }
         return true;
     }
 
@@ -890,16 +886,13 @@ export class Scope {
 
             }
         }
-        const directFiles = this.getImmediateFiles();
-        for (const file of directFiles) {
-            if (isBrsFile(file)) {
-                const namespaceTypes = file.getNamespaceSymbolTable();
+        this.enumerateBrsFiles((file) => {
+            const namespaceTypes = file.getNamespaceSymbolTable();
 
-                this.linkSymbolTableDisposables.push(
-                    ...this._allNamespaceTypeTable.mergeNamespaceSymbolTables(namespaceTypes)
-                );
-            }
-        }
+            this.linkSymbolTableDisposables.push(
+                ...this._allNamespaceTypeTable.mergeNamespaceSymbolTables(namespaceTypes)
+            );
+        });
         for (const [_, nsContainer] of this.namespaceLookup) {
             for (let nsStmt of nsContainer.namespaceStatements) {
                 this.linkSymbolTableDisposables.push(
diff --git a/src/SymbolTable.ts b/src/SymbolTable.ts
index 03a095fe..eca99445 100644
--- a/src/SymbolTable.ts
+++ b/src/SymbolTable.ts
@@ -167,7 +167,10 @@ export class SymbolTable implements SymbolTypeGetter {
      * Adds a new symbol to the table
      */
     addSymbol(name: string, data: ExtraSymbolData, type: BscType, bitFlags: SymbolTypeFlag) {
-        const key = name.toLowerCase();
+        if (!name) {
+            return;
+        }
+        const key = name?.toLowerCase();
         if (!this.symbolMap.has(key)) {
             this.symbolMap.set(key, []);
         }
diff --git a/src/astUtils/CachedLookups.ts b/src/astUtils/CachedLookups.ts
index d9f4a53f..2366572f 100644
--- a/src/astUtils/CachedLookups.ts
+++ b/src/astUtils/CachedLookups.ts
@@ -3,7 +3,7 @@ import type { AliasStatement, AssignmentStatement, AugmentedAssignmentStatement,
 import { Cache } from '../Cache';
 import { WalkMode, createVisitor } from './visitors';
 import type { Expression } from '../parser/AstNode';
-import { isAAMemberExpression, isBinaryExpression, isCallExpression, isDottedGetExpression, isFunctionExpression, isIndexedGetExpression, isMethodStatement, isNamespaceStatement, isNewExpression, isVariableExpression } from './reflection';
+import { isAAMemberExpression, isBinaryExpression, isCallExpression, isDottedGetExpression, isFunctionExpression, isIndexedGetExpression, isLiteralExpression, isMethodStatement, isNamespaceStatement, isNewExpression, isVariableExpression } from './reflection';
 import type { Parser } from '../parser/Parser';
 import { ParseMode } from '../parser/Parser';
 import type { Token } from '../lexer/Token';
@@ -217,7 +217,7 @@ export class CachedLookups {
                         break;
 
                         //when we hit a variable expression, we're definitely at the leftmost expression so stop
-                    } else if (isVariableExpression(node)) {
+                    } else if (isVariableExpression(node) || isLiteralExpression(node)) {
                         break;
                         //if
 
diff --git a/src/astUtils/reflection.ts b/src/astUtils/reflection.ts
index 3b67c26f..7de72937 100644
--- a/src/astUtils/reflection.ts
+++ b/src/astUtils/reflection.ts
@@ -23,7 +23,7 @@ import type { ObjectType } from '../types/ObjectType';
 import type { AstNode, Expression, Statement } from '../parser/AstNode';
 import type { AssetFile } from '../files/AssetFile';
 import { AstNodeKind } from '../parser/AstNode';
-import type { TypePropertyReferenceType, ReferenceType, BinaryOperatorReferenceType, ArrayDefaultTypeReferenceType } from '../types/ReferenceType';
+import type { TypePropertyReferenceType, ReferenceType, BinaryOperatorReferenceType, ArrayDefaultTypeReferenceType, AnyReferenceType } from '../types/ReferenceType';
 import type { EnumMemberType, EnumType } from '../types/EnumType';
 import type { UnionType } from '../types/UnionType';
 import type { UninitializedType } from '../types/UninitializedType';
@@ -386,7 +386,7 @@ export function isCallableType(target): target is BaseFunctionType {
     return isFunctionType(target) || isTypedFunctionType(target);
 }
 
-export function isAnyReferenceType(target): target is ReferenceType | TypePropertyReferenceType | BinaryOperatorReferenceType | ArrayDefaultTypeReferenceType {
+export function isAnyReferenceType(target): target is AnyReferenceType {
     const name = target?.__reflection?.name;
     return name === 'ReferenceType' || name === 'TypePropertyReferenceType' || name === 'BinaryOperatorReferenceType' || name === 'ArrayDefaultTypeReferenceType';
 }
diff --git a/src/bscPlugin/hover/HoverProcessor.ts b/src/bscPlugin/hover/HoverProcessor.ts
index 140e6a0e..90149b3b 100644
--- a/src/bscPlugin/hover/HoverProcessor.ts
+++ b/src/bscPlugin/hover/HoverProcessor.ts
@@ -70,7 +70,7 @@ export class HoverProcessor {
     }
 
     private getConstHover(token: Token, file: BrsFile, scope: Scope, expression: Expression) {
-        let containingNamespace = file.getNamespaceStatementForPosition(expression.range?.start)?.getName(ParseMode.BrighterScript);
+        let containingNamespace = file.getNamespaceStatementForPosition(expression?.range?.start)?.getName(ParseMode.BrighterScript);
         const fullName = util.getAllDottedGetParts(expression)?.map(x => x.text).join('.');
 
         //find a constant with this name
diff --git a/src/bscPlugin/validation/ScopeValidator.ts b/src/bscPlugin/validation/ScopeValidator.ts
index f0c6744d..521b5a59 100644
--- a/src/bscPlugin/validation/ScopeValidator.ts
+++ b/src/bscPlugin/validation/ScopeValidator.ts
@@ -1,6 +1,6 @@
 import { URI } from 'vscode-uri';
 import type { Range } from 'vscode-languageserver';
-import { isAliasStatement, isAssignmentStatement, isAssociativeArrayType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallableType, isClassStatement, isClassType, isComponentType, isConstStatement, isDottedGetExpression, isDynamicType, isEnumMemberType, isEnumStatement, isEnumType, isFunctionExpression, isFunctionParameterExpression, isFunctionStatement, isInterfaceStatement, isLiteralExpression, isNamespaceStatement, isNamespaceType, isNewExpression, isNumberType, isObjectType, isPrimitiveType, isReferenceType, isStringType, isTypedFunctionType, isUnionType, isVariableExpression, isXmlScope } from '../../astUtils/reflection';
+import { isAliasStatement, isAssignmentStatement, isAssociativeArrayType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallableType, isClassStatement, isClassType, isComponentType, isDottedGetExpression, isDynamicType, isEnumMemberType, isEnumType, isFunctionExpression, isFunctionParameterExpression, isLiteralExpression, isNamespaceStatement, isNamespaceType, isNewExpression, isNumberType, isObjectType, isPrimitiveType, isReferenceType, isStringType, isTypedFunctionType, isUnionType, isVariableExpression, isXmlScope } from '../../astUtils/reflection';
 import { Cache } from '../../Cache';
 import type { DiagnosticInfo } from '../../DiagnosticMessages';
 import { DiagnosticMessages } from '../../DiagnosticMessages';
@@ -52,13 +52,16 @@ export class ScopeValidator {
      */
     private event: OnScopeValidateEvent;
 
+    private metrics = new Map<string, number>();
+
+
     public processEvent(event: OnScopeValidateEvent) {
         this.event = event;
         if (this.event.program.globalScope === this.event.scope) {
             return;
         }
         this.event.program.diagnostics.clearByFilter({ scope: this.event.scope, tag: ScopeValidatorDiagnosticTag });
-
+        this.metrics.clear();
         this.walkFiles();
         this.detectDuplicateEnums();
         this.flagDuplicateFunctionDeclarations();
@@ -70,6 +73,14 @@ export class ScopeValidator {
             //validate component interface
             this.validateXmlInterface(event.scope);
         }
+
+        this.event.program.logger.debug(this.event.scope.name, 'metrics:');
+        let total = 0;
+        for (const [filePath, num] of this.metrics) {
+            this.event.program.logger.debug(' - ', filePath, num);
+            total += num;
+        }
+        this.event.program.logger.debug(this.event.scope.name, 'total segments validated', total);
     }
 
     public reset() {
@@ -90,34 +101,24 @@ export class ScopeValidator {
             if (thisFileHasChanges) {
                 this.event.program.diagnostics.clearByFilter({ scope: this.event.scope, file: file, tag: ScopeValidatorDiagnosticTag });
             }
-
             this.detectVariableNamespaceCollisions(file);
 
             if (thisFileHasChanges || this.doesFileProvideChangedSymbol(file, this.event.changedSymbols)) {
                 this.diagnosticDetectFunctionCollisions(file);
-                this.detectNameCollisions(file);
             }
+
         });
 
         this.event.scope.enumerateOwnFiles((file) => {
             if (isBrsFile(file)) {
                 const thisFileHasChanges = this.event.changedFiles.includes(file);
 
-                const thisFileRequiresChangedSymbol = this.doesFileRequireChangedSymbol(file);
-
                 const hasUnvalidatedSegments = file.validationSegmenter.hasUnvalidatedSegments();
 
-                if (hasChangeInfo && !thisFileRequiresChangedSymbol && !thisFileHasChanges && !hasUnvalidatedSegments) {
-                    // this file does not require a symbol that has changed, and this file has not changed
-
-                    if (!this.doesFileAssignChangedSymbol(file)) {
-                        // this file does not have a variable assignment that needs to be checked
-                        return;
-                    }
-
+                if (hasChangeInfo && !hasUnvalidatedSegments) {
+                    return;
                 }
 
-
                 const validationVisitor = createVisitor({
                     VariableExpression: (varExpr) => {
                         this.validateVariableAndDottedGetExpressions(file, varExpr);
@@ -174,43 +175,30 @@ export class ScopeValidator {
                         });
                     }
                 });
-                const segmentsToWalkForValidation = (thisFileHasChanges || !hasChangeInfo)
-                    ? file.validationSegmenter.segmentsForValidation // validate everything in the file
-                    : file.getValidationSegments(this.event.changedSymbols); // validate only what's needed in the file
+                // validate only what's needed in the file
+
+                const segmentsToWalkForValidation = thisFileHasChanges
+                    ? file.validationSegmenter.getAllUnvalidatedSegments()
+                    : file.validationSegmenter.getSegmentsWithChangedSymbols(this.event.changedSymbols);
 
+                let segmentsValidated = 0;
                 for (const segment of segmentsToWalkForValidation) {
-                    if (!file.validationSegmenter.checkIfSegmentNeedRevalidation(segment)) {
+                    if (!file.validationSegmenter.checkIfSegmentNeedsRevalidation(segment, this.event.changedSymbols)) {
                         continue;
                     }
                     this.currentSegmentBeingValidated = segment;
                     this.event.program.diagnostics.clearByFilter({ scope: this.event.scope, file: file, segment: segment });
+                    segmentsValidated++;
                     segment.walk(validationVisitor, {
                         walkMode: InsideSegmentWalkMode
                     });
                     file.markSegmentAsValidated(segment);
                 }
+                this.metrics.set(file.pkgPath, segmentsValidated);
             }
         });
     }
 
-    private doesFileRequireChangedSymbol(file: BrsFile) {
-        let thisFileRequiresChangedSymbol = false;
-        for (let requiredSymbol of file.requiredSymbols) {
-            // eslint-disable-next-line no-bitwise
-            for (const flag of [SymbolTypeFlag.runtime, SymbolTypeFlag.typetime]) {
-                // eslint-disable-next-line no-bitwise
-                if (flag & requiredSymbol.flags) {
-                    const changeSymbolSetForFlag = this.event.changedSymbols.get(flag);
-                    if (util.setContainsUnresolvedSymbol(changeSymbolSetForFlag, requiredSymbol)) {
-                        thisFileRequiresChangedSymbol = true;
-                        break;
-                    }
-                }
-            }
-        }
-        return thisFileRequiresChangedSymbol;
-    }
-
     private doesFileProvideChangedSymbol(file: BrsFile, changedSymbols: Map<SymbolTypeFlag, Set<string>>) {
         if (!changedSymbols) {
             return true;
@@ -228,18 +216,6 @@ export class ScopeValidator {
         return false;
     }
 
-    private doesFileAssignChangedSymbol(file: BrsFile) {
-        let thisFileAssignsChangedSymbol = false;
-        const runTimeChangedSymbolSet = this.event.changedSymbols.get(SymbolTypeFlag.runtime);
-        for (let assignedSymbol of file.assignedSymbols) {
-            if (runTimeChangedSymbolSet.has(assignedSymbol.token.text.toLowerCase())) {
-                thisFileAssignsChangedSymbol = true;
-                break;
-            }
-        }
-        return thisFileAssignsChangedSymbol;
-    }
-
     private currentSegmentBeingValidated: AstNode;
 
 
@@ -519,7 +495,7 @@ export class ScopeValidator {
             // treat members as dynamic - they could have been set without the type system's knowledge
             return;
         }
-        if (!expectedLHSType?.isResolvable()) {
+        if (!expectedLHSType || !expectedLHSType?.isResolvable()) {
             this.addMultiScopeDiagnostic({
                 file: file as BscFile,
                 ...DiagnosticMessages.cannotFindName(typeChainScan.itemName, typeChainScan.fullNameOfItem, typeChainScan.itemParentTypeName, this.getParentTypeDescriptor(typeChainScan)),
@@ -563,11 +539,7 @@ export class ScopeValidator {
         const actualRHSType = this.getNodeTypeWrapper(file, assignStmt.value, getTypeOpts);
         const compatibilityData: TypeCompatibilityData = {};
         if (!expectedLHSType || !expectedLHSType.isResolvable()) {
-            this.addMultiScopeDiagnostic({
-                ...DiagnosticMessages.cannotFindName(assignStmt.typeExpression.getName(ParseMode.BrighterScript)),
-                range: assignStmt.typeExpression.range,
-                file: file
-            });
+            // LHS is not resolvable... handled elsewhere
         } else if (!expectedLHSType?.isTypeCompatible(actualRHSType, compatibilityData)) {
             this.addMultiScopeDiagnostic({
                 ...DiagnosticMessages.assignmentTypeMismatch(actualRHSType.toString(), expectedLHSType.toString(), compatibilityData),
@@ -798,8 +770,8 @@ export class ScopeValidator {
                         range: typeChainScan.range
                     });
                 }
-            }
 
+            }
         }
         if (isUsedAsType) {
             return;
@@ -1104,117 +1076,10 @@ export class ScopeValidator {
 
                     });
                 }
-
-                //find any functions that have the same name as a class
-                const klassLink = this.event.scope.getClassFileLink(lowerFuncName);
-                if (klassLink) {
-                    this.addMultiScopeDiagnostic({
-                        ...DiagnosticMessages.functionCannotHaveSameNameAsClass(funcName),
-                        range: func.nameRange,
-                        file: file,
-                        relatedInformation: [{
-                            location: util.createLocation(
-                                URI.file(klassLink.file.srcPath).toString(),
-                                klassLink.item.tokens.name.range
-                            ),
-                            message: 'Original class declared here'
-                        }]
-                    });
-                }
             }
         }
     }
 
-    private detectNameCollisions(file: BrsFile) {
-        // eslint-disable-next-line @typescript-eslint/dot-notation
-        for (let nsStmt of file['_cachedLookups'].namespaceStatements) {
-            this.validateNameCollision(file, nsStmt, nsStmt.getNameParts()?.[0]);
-
-        }
-        // eslint-disable-next-line @typescript-eslint/dot-notation
-        for (let classStmt of file['_cachedLookups'].classStatements) {
-            this.validateNameCollision(file, classStmt, classStmt.tokens.name);
-
-        }
-        // eslint-disable-next-line @typescript-eslint/dot-notation
-        for (let ifaceStmt of file['_cachedLookups'].interfaceStatements) {
-            this.validateNameCollision(file, ifaceStmt, ifaceStmt.tokens.name);
-
-        }
-        // eslint-disable-next-line @typescript-eslint/dot-notation
-        for (let constStmt of file['_cachedLookups'].constStatements) {
-            this.validateNameCollision(file, constStmt, constStmt.tokens.name);
-        }
-
-        // eslint-disable-next-line @typescript-eslint/dot-notation
-        for (let enumStmt of file['_cachedLookups'].enumStatements) {
-            this.validateNameCollision(file, enumStmt, enumStmt.tokens.name);
-
-        }
-    }
-
-
-    validateNameCollision(file: BrsFile, node: AstNode, nameIdentifier: Token) {
-        const name = nameIdentifier?.text;
-        if (!name || !node) {
-            return;
-        }
-        const nameRange = nameIdentifier.range;
-
-        const containingNamespace = node.findAncestor<NamespaceStatement>(isNamespaceStatement)?.getName(ParseMode.BrighterScript);
-        const containingNamespaceLower = containingNamespace?.toLowerCase();
-        const links = this.event.scope.getAllFileLinks(name, containingNamespace, !isNamespaceStatement(node));
-        for (let link of links) {
-            if (!link || link.item === node) {
-                // refers to same node
-                continue;
-            }
-            if (isNamespaceStatement(link.item) && isNamespaceStatement(node)) {
-                // namespace can be declared multiple times
-                continue;
-            }
-            if (isFunctionStatement(link.item) || link.file?.destPath === 'global') {
-                const linkItemNamespaceLower = link.item?.findAncestor<NamespaceStatement>(isNamespaceStatement)?.getName(ParseMode.BrighterScript)?.toLowerCase();
-                if (!(containingNamespaceLower && linkItemNamespaceLower) || linkItemNamespaceLower !== containingNamespaceLower) {
-
-                    // the thing found is a function OR from global (which is also a function)
-                    if (isNamespaceStatement(node) ||
-                        isEnumStatement(node) ||
-                        isConstStatement(node) ||
-                        isInterfaceStatement(node)) {
-                        // these are not callable functions in transpiled code - ignore them
-                        continue;
-                    }
-                }
-            }
-
-            const thisNodeKindName = util.getAstNodeFriendlyName(node);
-            const thatNodeKindName = link.file.srcPath === 'global' ? 'Global Function' : util.getAstNodeFriendlyName(link.item) ?? '';
-
-            let thatNameRange = (link.item as any)?.tokens?.name?.range ?? link.item?.range;
-
-            if (isNamespaceStatement(link.item)) {
-                thatNameRange = (link.item as NamespaceStatement).getNameParts()?.[0]?.range;
-            }
-
-            const relatedInformation = thatNameRange ? [{
-                message: `${thatNodeKindName} declared here`,
-                location: util.createLocation(
-                    URI.file(link.file?.srcPath).toString(),
-                    thatNameRange
-                )
-            }] : undefined;
-
-            this.addMultiScopeDiagnostic({
-                file: file,
-                ...DiagnosticMessages.nameCollision(thisNodeKindName, thatNodeKindName, name),
-                range: nameRange,
-                relatedInformation: relatedInformation
-            });
-        }
-
-    }
-
     public detectShadowedLocalVar(file: BrsFile, varDeclaration: { name: string; type: BscType; nameRange: Range }) {
         const varName = varDeclaration.name;
         const lowerVarName = varName.toLowerCase();
diff --git a/src/files/BrsFile.Class.spec.ts b/src/files/BrsFile.Class.spec.ts
index e709506c..c36fe684 100644
--- a/src/files/BrsFile.Class.spec.ts
+++ b/src/files/BrsFile.Class.spec.ts
@@ -1560,10 +1560,7 @@ describe('BrsFile BrighterScript classes', () => {
             `);
             program.validate();
             expectDiagnostics(program, [{
-                ...DiagnosticMessages.cannotFindName('GroundedBird', 'Vertibrates.GroundedBird', 'Vertibrates', 'namespace'),
-                relatedInformation: [{
-                    message: `In scope 'source'`
-                }]
+                ...DiagnosticMessages.cannotFindName('GroundedBird', 'Vertibrates.GroundedBird', 'Vertibrates', 'namespace')
             }]);
         });
 
@@ -1673,7 +1670,7 @@ describe('BrsFile BrighterScript classes', () => {
         `);
         program.validate();
         expectDiagnosticsIncludes(program, [
-            DiagnosticMessages.nameCollision('Class', 'Class', 'Animal').message
+            DiagnosticMessages.nameCollision('Class', 'Class', 'NameA.NameB.Animal').message
         ]);
     });
 
@@ -1705,8 +1702,9 @@ describe('BrsFile BrighterScript classes', () => {
             end sub
         `);
         program.validate();
-        expectDiagnosticsIncludes(program, [
-            DiagnosticMessages.functionCannotHaveSameNameAsClass('Animal').message
+        expectDiagnostics(program, [
+            DiagnosticMessages.nameCollision('Class', 'Function', 'Animal').message,
+            DiagnosticMessages.nameCollision('Function', 'Class', 'Animal').message
         ]);
     });
 
@@ -1719,7 +1717,8 @@ describe('BrsFile BrighterScript classes', () => {
         `);
         program.validate();
         expectDiagnosticsIncludes(program, [
-            DiagnosticMessages.functionCannotHaveSameNameAsClass('animal').message
+            DiagnosticMessages.nameCollision('Class', 'Function', 'animal').message,
+            DiagnosticMessages.nameCollision('Function', 'Class', 'ANIMAL').message
         ]);
     });
 
diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts
index 177ee96d..6c838e09 100644
--- a/src/files/BrsFile.spec.ts
+++ b/src/files/BrsFile.spec.ts
@@ -4333,7 +4333,7 @@ describe('BrsFile', () => {
             expect(mainFile.requiredSymbols.length).to.eq(0);
         });
 
-        it('should not include symbols in imported file', () => {
+        it('should include symbols in imported file', () => {
             const otherFile: BrsFile = program.setFile('source/other.bs', `
                 namespace alpha
                     const PI = 3.14
@@ -4348,10 +4348,10 @@ describe('BrsFile', () => {
                 end namespace
             `);
             validateFile(otherFile, mainFile);
-            expect(mainFile.requiredSymbols.length).to.eq(0);
+            expect(mainFile.requiredSymbols.length).to.eq(1);
         });
 
-        it('should not include symbols in imported file of imported file', () => {
+        it('should  include symbols in imported file of imported file', () => {
             const deepFile: BrsFile = program.setFile('source/deep.bs', `
                 namespace alpha
                     const SOME_VALUE = 2
@@ -4372,10 +4372,10 @@ describe('BrsFile', () => {
                 end namespace
             `);
             validateFile(otherFile, mainFile, deepFile);
-            expect(mainFile.requiredSymbols.length).to.eq(0);
+            expect(mainFile.requiredSymbols.length).to.eq(2);
         });
 
-        it('should not have problems with circular references of imports', () => {
+        it('should ignore imports even with circular references', () => {
             const deepFile: BrsFile = program.setFile('source/deep.bs', `
                 import "pkg:/source/main.bs"
                 namespace alpha
@@ -4400,7 +4400,7 @@ describe('BrsFile', () => {
                 end namespace
             `);
             validateFile(otherFile, mainFile, deepFile);
-            expect(mainFile.requiredSymbols.length).to.eq(0);
+            expect(mainFile.requiredSymbols.length).to.eq(2);
         });
     });
 
@@ -4419,9 +4419,9 @@ describe('BrsFile', () => {
             validateFile(mainFile);
             const runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
             expect(runtimeSymbols.size).to.eq(2);
-            const someFuncType = runtimeSymbols.get('somefunc').type;
+            const someFuncType = runtimeSymbols.get('somefunc').symbol.type;
             expectTypeToBe(someFuncType, TypedFunctionType);
-            const someFunc2Type = runtimeSymbols.get('somefunc2').type;
+            const someFunc2Type = runtimeSymbols.get('somefunc2').symbol.type;
             expectTypeToBe(someFunc2Type, TypedFunctionType);
         });
 
@@ -4435,7 +4435,7 @@ describe('BrsFile', () => {
 
             const runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
             expect(runtimeSymbols.size).to.eq(1);
-            const someFuncType = runtimeSymbols.get('somefunc').type;
+            const someFuncType = runtimeSymbols.get('somefunc').symbol.type;
             expectTypeToBe(someFuncType, TypedFunctionType);
             const requiredSymbols = mainFile.requiredSymbols.map(x => x.typeChain[0].name);
             expect(requiredSymbols).to.have.same.members(['OtherFileType', 'OtherFileType']);
@@ -4465,14 +4465,14 @@ describe('BrsFile', () => {
 
             const runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
             expect(runtimeSymbols.size).to.eq(3);
-            expectTypeToBe(runtimeSymbols.get('klass').type, ClassType);
-            expectTypeToBe(runtimeSymbols.get('klass2').type, ClassType);
-            expectTypeToBe(runtimeSymbols.get('klass3').type, ClassType);
+            expectTypeToBe(runtimeSymbols.get('klass').symbol.type, ClassType);
+            expectTypeToBe(runtimeSymbols.get('klass2').symbol.type, ClassType);
+            expectTypeToBe(runtimeSymbols.get('klass3').symbol.type, ClassType);
             const typetimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.typetime);
             expect(typetimeSymbols.size).to.eq(3);
-            expectTypeToBe(runtimeSymbols.get('klass').type, ClassType);
-            expectTypeToBe(runtimeSymbols.get('klass2').type, ClassType);
-            expectTypeToBe(runtimeSymbols.get('klass3').type, ClassType);
+            expectTypeToBe(runtimeSymbols.get('klass').symbol.type, ClassType);
+            expectTypeToBe(runtimeSymbols.get('klass2').symbol.type, ClassType);
+            expectTypeToBe(runtimeSymbols.get('klass3').symbol.type, ClassType);
         });
 
         it('includes other types defined in the file', () => {
@@ -4493,12 +4493,12 @@ describe('BrsFile', () => {
             validateFile(mainFile);
             const runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
             expect(runtimeSymbols.size).to.eq(2);
-            expectTypeToBe(runtimeSymbols.get('myenum').type, EnumType);
-            expectTypeToBe(runtimeSymbols.get('mynamespace.myconst').type, FloatType);
+            expectTypeToBe(runtimeSymbols.get('myenum').symbol.type, EnumType);
+            expectTypeToBe(runtimeSymbols.get('mynamespace.myconst').symbol.type, FloatType);
             const typetimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.typetime);
             expect(typetimeSymbols.size).to.eq(2);
-            expectTypeToBe(typetimeSymbols.get('myinterface').type, InterfaceType);
-            expectTypeToBe(runtimeSymbols.get('myenum').type, EnumType);
+            expectTypeToBe(typetimeSymbols.get('myinterface').symbol.type, InterfaceType);
+            expectTypeToBe(runtimeSymbols.get('myenum').symbol.type, EnumType);
         });
 
         describe('changes', () => {
diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts
index aa4dc6c2..7326b8ca 100644
--- a/src/files/BrsFile.ts
+++ b/src/files/BrsFile.ts
@@ -19,7 +19,7 @@ import { DynamicType } from '../types/DynamicType';
 import { standardizePath as s, util } from '../util';
 import { BrsTranspileState } from '../parser/BrsTranspileState';
 import { serializeError } from 'serialize-error';
-import { isClassStatement, isDottedGetExpression, isFunctionExpression, isFunctionStatement, isNamespaceStatement, isVariableExpression, isImportStatement, isEnumStatement, isConstStatement, isAnyReferenceType, isNamespaceType, isReferenceType, isCallableType, isBrsFile } from '../astUtils/reflection';
+import { isClassStatement, isDottedGetExpression, isFunctionExpression, isFunctionStatement, isNamespaceStatement, isVariableExpression, isImportStatement, isEnumStatement, isConstStatement, isAnyReferenceType, isNamespaceType, isReferenceType, isCallableType } from '../astUtils/reflection';
 import { createVisitor, WalkMode } from '../astUtils/visitors';
 import type { DependencyChangedEvent, DependencyGraph } from '../DependencyGraph';
 import { CommentFlagProcessor } from '../CommentFlagProcessor';
@@ -42,7 +42,11 @@ import { NamespaceType } from '../types';
 import type { BscFile } from './BscFile';
 import { DefinitionProvider } from '../bscPlugin/definition/DefinitionProvider';
 
-export type ProvidedSymbolMap = Map<SymbolTypeFlag, Map<string, BscSymbol>>;
+export interface ProvidedSymbol {
+    symbol: BscSymbol;
+    duplicates: BscSymbol[];
+}
+export type ProvidedSymbolMap = Map<SymbolTypeFlag, Map<string, ProvidedSymbol>>;
 export type ChangedSymbolMap = Map<SymbolTypeFlag, Set<string>>;
 
 export interface ProvidedSymbolInfo {
@@ -312,11 +316,6 @@ export class BrsFile implements BscFile {
 
     public onDependenciesChanged(event: DependencyChangedEvent) {
         this.resolveTypedef();
-        this.unlinkNamespaceSymbolTables();
-        this.cache?.delete('namespaceSymbolTable');
-        this.cache?.delete('requiredSymbols');
-
-        this.validationSegmenter.unValidateAllSegments();
     }
 
     /**
@@ -1007,41 +1006,19 @@ export class BrsFile implements BscFile {
         }
     }
 
-    public validationSegmenter = new AstValidationSegmenter();
+    public validationSegmenter = new AstValidationSegmenter(this);
 
     public getNamespaceSymbolTable(allowCache = true) {
-        const makeImportTreeNamespaceTable = () => {
-            const nsTable = new SymbolTable(`File Complete NamespaceTypes ${this.destPath}`, () => this.program.globalScope.symbolTable);
-            this.updateWithDependenciesNamespaceTables(nsTable, new Set());
-            return nsTable;
-        };
         if (!allowCache) {
-            return makeImportTreeNamespaceTable();
-        }
-        return this.cache?.getOrAdd(`namespaceSymbolTable`, makeImportTreeNamespaceTable);
-    }
-
-    private updateWithDependenciesNamespaceTables(symbolTableToUpdate: SymbolTable, filesToSkip: Set<string>) {
-        symbolTableToUpdate.mergeNamespaceSymbolTables(this.getOwnNamespaceSymbolTable());
-        filesToSkip.add(this.destPath.toLowerCase());
-        for (const filePath of this.dependencies) {
-            if (filesToSkip.has(filePath.toLowerCase())) {
-                continue;
-            }
-            const importedFile = this.program.getFile<BrsFile>(filePath);
-            if (!isBrsFile(importedFile) || importedFile === this) {
-                continue;
-            }
-            importedFile.updateWithDependenciesNamespaceTables(symbolTableToUpdate, filesToSkip);
+            return this.constructNamespaceSymbolTable();
         }
+        return this.cache?.getOrAdd(`namespaceSymbolTable`, () => this.constructNamespaceSymbolTable());
     }
 
-    public getOwnNamespaceSymbolTable() {
-        return this.cache?.getOrAdd(`ownNamespaceSymbolTable`, () => {
-            const nsTable = new SymbolTable(`File NamespaceTypes ${this.destPath}`, () => this.program.globalScope.symbolTable);
-            this.populateNameSpaceSymbolTable(nsTable);
-            return nsTable;
-        });
+    private constructNamespaceSymbolTable() {
+        const nsTable = new SymbolTable(`File NamespaceTypes ${this.destPath}`, () => this.program?.globalScope.symbolTable);
+        this.populateNameSpaceSymbolTable(nsTable);
+        return nsTable;
     }
 
     public processSymbolInformation() {
@@ -1110,12 +1087,6 @@ export class BrsFile implements BscFile {
         }
     }
 
-    public getValidationSegments(changedSymbols: Map<SymbolTypeFlag, Set<string>>) {
-        const segments = this.validationSegmenter.getSegments(changedSymbols);
-        return segments;
-    }
-
-
     public get requiredSymbols() {
         return this.cache.getOrAdd(`requiredSymbols`, () => {
             const allNeededSymbolSets = this.validationSegmenter.unresolvedSegmentsSymbols.values();
@@ -1127,20 +1098,19 @@ export class BrsFile implements BscFile {
             for (const setOfSymbols of allNeededSymbolSets) {
                 for (const symbol of setOfSymbols) {
                     const fullSymbolKey = symbol.typeChain.map(tce => tce.name).join('.').toLowerCase();
-                    //for (const flag of [SymbolTypeFlag.runtime, SymbolTypeFlag.typetime]) {
-                    // eslint-disable-next-line no-bitwise
                     const flag = symbol.endChainFlags;
-                    //  if (symbol.endChainFlags & flag) {
                     if (this.providedSymbols.symbolMap.get(flag)?.has(fullSymbolKey)) {
                         // this catches namespaced things
                         continue;
                     }
+                    if (this.ast.getSymbolTable().hasSymbol(fullSymbolKey, flag)) {
+                        //catches aliases
+                        continue;
+                    }
                     if (!addedSymbols.get(flag)?.has(fullSymbolKey)) {
                         requiredSymbols.push(symbol);
                         addedSymbols.get(flag)?.add(fullSymbolKey);
                     }
-                    // }
-                    //}
                 }
             }
             return requiredSymbols;
@@ -1167,9 +1137,12 @@ export class BrsFile implements BscFile {
     }
 
     private getProvidedSymbols() {
-        const symbolMap = new Map<SymbolTypeFlag, Map<string, BscSymbol>>();
-        const runTimeSymbolMap = new Map<string, BscSymbol>();
-        const typeTimeSymbolMap = new Map<string, BscSymbol>();
+        const symbolMap = new Map<SymbolTypeFlag, Map<string, ProvidedSymbol>>();
+        const runTimeSymbolMap = new Map<string, ProvidedSymbol>();
+        const typeTimeSymbolMap = new Map<string, ProvidedSymbol>();
+        const referenceSymbolMap = new Map<SymbolTypeFlag, Map<string, ProvidedSymbol>>();
+        const referenceRunTimeSymbolMap = new Map<string, ProvidedSymbol>();
+        const referenceTypeTimeSymbolMap = new Map<string, ProvidedSymbol>();
 
         const tablesToGetSymbolsFrom: Array<{ table: SymbolTable; namePrefixLower?: string }> = [{
             table: this.parser.symbolTable
@@ -1182,25 +1155,55 @@ export class BrsFile implements BscFile {
             });
         }
 
+        function getAnyDuplicates(symbolNameLower: string, providedSymbolMap: Map<string, ProvidedSymbol>, referenceProvidedSymbolMap: Map<string, ProvidedSymbol>) {
+            if (symbolNameLower === 'm') {
+                return [];
+            }
+            let duplicates = [] as Array<BscSymbol>;
+            let existingSymbol = providedSymbolMap.get(symbolNameLower);
+            if (existingSymbol) {
+                duplicates.push(existingSymbol.symbol, ...existingSymbol.duplicates);
+            }
+            existingSymbol = referenceProvidedSymbolMap.get(symbolNameLower);
+            if (existingSymbol) {
+                duplicates.push(existingSymbol.symbol, ...existingSymbol.duplicates);
+            }
+
+            return duplicates;
+        }
+
         for (const symbolTable of tablesToGetSymbolsFrom) {
             const runTimeSymbols = symbolTable.table.getOwnSymbols(SymbolTypeFlag.runtime);
             const typeTimeSymbols = symbolTable.table.getOwnSymbols(SymbolTypeFlag.typetime);
 
             for (const symbol of runTimeSymbols) {
-                if (!isAnyReferenceType(symbol.type) && symbol.name.toLowerCase() !== 'm') {
-                    const symbolNameLower = symbolTable.namePrefixLower
-                        ? `${symbolTable.namePrefixLower}.${symbol.name.toLowerCase()}`
-                        : symbol.name.toLowerCase();
-                    runTimeSymbolMap.set(symbolNameLower, symbol);
+                const symbolNameLower = symbolTable.namePrefixLower
+                    ? `${symbolTable.namePrefixLower}.${symbol.name.toLowerCase()}`
+                    : symbol.name.toLowerCase();
+                if (symbolNameLower === 'm') {
+                    continue;
+                }
+                const duplicates = getAnyDuplicates(symbolNameLower, runTimeSymbolMap, referenceRunTimeSymbolMap);
+
+                if (!isAnyReferenceType(symbol.type)) {
+                    runTimeSymbolMap.set(symbolNameLower, { symbol: symbol, duplicates: duplicates });
+                } else {
+                    referenceRunTimeSymbolMap.set(symbolNameLower, { symbol: symbol, duplicates: duplicates });
                 }
             }
 
             for (const symbol of typeTimeSymbols) {
+                const symbolNameLower = symbolTable.namePrefixLower
+                    ? `${symbolTable.namePrefixLower}.${symbol.name.toLowerCase()}`
+                    : symbol.name.toLowerCase();
+                if (symbolNameLower === 'm') {
+                    continue;
+                }
+                const duplicates = getAnyDuplicates(symbolNameLower, typeTimeSymbolMap, referenceTypeTimeSymbolMap);
                 if (!isAnyReferenceType(symbol.type)) {
-                    const symbolNameLower = symbolTable.namePrefixLower
-                        ? `${symbolTable.namePrefixLower}.${symbol.name.toLowerCase()}`
-                        : symbol.name.toLowerCase();
-                    typeTimeSymbolMap.set(symbolNameLower, symbol);
+                    typeTimeSymbolMap.set(symbolNameLower, { symbol: symbol, duplicates: duplicates });
+                } else {
+                    referenceTypeTimeSymbolMap.set(symbolNameLower, { symbol: symbol, duplicates: duplicates });
                 }
             }
         }
@@ -1208,6 +1211,9 @@ export class BrsFile implements BscFile {
         symbolMap.set(SymbolTypeFlag.runtime, runTimeSymbolMap);
         symbolMap.set(SymbolTypeFlag.typetime, typeTimeSymbolMap);
 
+        referenceSymbolMap.set(SymbolTypeFlag.runtime, referenceRunTimeSymbolMap);
+        referenceSymbolMap.set(SymbolTypeFlag.typetime, referenceTypeTimeSymbolMap);
+
         const changes = new Map<SymbolTypeFlag, Set<string>>();
         changes.set(SymbolTypeFlag.runtime, new Set<string>());
         changes.set(SymbolTypeFlag.typetime, new Set<string>());
@@ -1227,9 +1233,9 @@ export class BrsFile implements BscFile {
                 continue;
 
             }
-            for (const [symbolKey, symbol] of newSymbolMapForFlag) {
-                const symbolType = symbol.type;
-                const previousType = oldSymbolMapForFlag?.get(symbolKey)?.type;
+            for (const [symbolKey, symbolObj] of newSymbolMapForFlag) {
+                const symbolType = symbolObj.symbol.type;
+                const previousType = oldSymbolMapForFlag?.get(symbolKey)?.symbol?.type;
                 previousSymbolsCheckedForFlag.add(symbolKey);
                 if (!previousType) {
                     changesForFlag.add(symbolKey);
@@ -1248,7 +1254,8 @@ export class BrsFile implements BscFile {
         }
         return {
             symbolMap: symbolMap,
-            changes: changes
+            changes: changes,
+            referenceSymbolMap: referenceSymbolMap
         };
     }
 
diff --git a/src/interfaces.ts b/src/interfaces.ts
index de9304ed..ae98bf6b 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -35,7 +35,8 @@ export enum DiagnosticOrigin {
     Program = 'Program',
     Scope = 'Scope',
     File = 'File',
-    ASTSegment = 'AstSegment'
+    ASTSegment = 'AstSegment',
+    CrossScope = 'CrossScope'
 }
 
 export interface DiagnosticContext {
@@ -658,8 +659,9 @@ export interface TranspileEntry {
 
 
 export interface ScopeValidationOptions {
-    changedFiles?: BscFile[];
+    filesToBeValidatedInScopeContext?: Set<BscFile>;
     changedSymbols?: Map<SymbolTypeFlag, Set<string>>;
+    changedFiles?: Set<BscFile>;
     force?: boolean;
     initialValidation?: boolean;
 }
@@ -951,9 +953,7 @@ export class TypeChainEntry {
         this._range = options.range;
         this.separatorToken = options.separatorToken ?? createToken(TokenKind.Dot);
         this.astNode = options.astNode;
-    }
-    get isResolved() {
-        return this.type?.isResolvable();
+        this.isResolved = this.type?.isResolvable();
     }
 
     get range() {
@@ -965,6 +965,7 @@ export class TypeChainEntry {
     public readonly data: ExtraSymbolData;
     private readonly _range: Range;
     public readonly separatorToken: Token;
+    public isResolved: boolean;
     public astNode: AstNode;
 }
 
diff --git a/src/roku-types/data.json b/src/roku-types/data.json
index bcd747db..2f98351a 100644
--- a/src/roku-types/data.json
+++ b/src/roku-types/data.json
@@ -1,5 +1,5 @@
 {
-    "generatedDate": "2024-05-10T15:18:59.052Z",
+    "generatedDate": "2024-05-30T13:07:44.633Z",
     "nodes": {
         "animation": {
             "description": "Extends [**AnimationBase**](https://developer.roku.com/docs/references/scenegraph/abstract-nodes/animationbase.md\n\nThe Animation node class provides animations of renderable nodes, by applying interpolator functions to the values in specified renderable node fields. For an animation to take effect, an Animation node definition must include a child field interpolator node ([FloatFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/floatfieldinterpolator.md\"FloatFieldInterpolator\"), [Vector2DFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/vector2dfieldinterpolator.md\"Vector2DFieldInterpolator\"), [ColorFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/colorfieldinterpolator.md\"ColorFieldInterpolator\")) definition for each renderable node field that is animated.\n\nThe Animation node class provides a simple linear interpolator function, where the animation takes place smoothly and simply from beginning to end. The Animation node class also provides several more complex interpolator functions to allow custom animation effects. For example, you can move a graphic image around the screen at differing speeds and curved trajectories at different times in the animation by specifying the appropriate function in the easeFunction field (quadratic and exponential are two examples of functions that can be specified). The interpolator functions are divided into two parts: the beginning of the animation (ease-in), and the end of the animation (ease-out). You can apply a specified interpolator function to either or both ease-in and ease-out, or specify no function for either or both (which is the linear function). You can also change the portion of the animation that is ease-in and ease-out to arbitrary fractional values for a quadratic interpolator function applied to both ease-in and ease-out.",
@@ -4543,7 +4543,7 @@
                 }
             ],
             "interfaces": [],
-            "name": "TrickPlayBar",
+            "name": "ProgressBar",
             "url": "https://developer.roku.com/en-ca/docs/references/scenegraph/media-playback-nodes/video.md#ui-fields"
         },
         "progressdialog": {
diff --git a/src/types/ClassType.spec.ts b/src/types/ClassType.spec.ts
index df4210a9..0817fff0 100644
--- a/src/types/ClassType.spec.ts
+++ b/src/types/ClassType.spec.ts
@@ -14,8 +14,8 @@ describe('ClassType', () => {
         const superKlass = new ClassType('SuperKlass');
         const subKlass = new ClassType('SubKlass', superKlass);
 
-        expect(subKlass.superClass).to.exist;
-        expect(subKlass.superClass.toString()).to.equal('SuperKlass');
+        expect(subKlass.parentType).to.exist;
+        expect(subKlass.parentType.toString()).to.equal('SuperKlass');
     });
 
     it('should be assignable to a super klass, or higher ancestor', () => {
diff --git a/src/types/ClassType.ts b/src/types/ClassType.ts
index f5ddb6c9..bb513b59 100644
--- a/src/types/ClassType.ts
+++ b/src/types/ClassType.ts
@@ -10,7 +10,7 @@ import { isUnionTypeCompatible } from './helpers';
 
 export class ClassType extends InheritableType {
 
-    constructor(public name: string, public readonly superClass?: ClassType | ReferenceType) {
+    constructor(public name: string, superClass?: ClassType | ReferenceType) {
         super(name, superClass);
     }
 
@@ -39,6 +39,7 @@ export class ClassType extends InheritableType {
     private builtInMemberTable: SymbolTable;
 
     getBuiltInMemberTable(): SymbolTable {
+
         if (!this.parentType) {
             if (this.builtInMemberTable) {
                 return this.builtInMemberTable;
diff --git a/src/types/ReferenceType.ts b/src/types/ReferenceType.ts
index 55ad699d..a5728744 100644
--- a/src/types/ReferenceType.ts
+++ b/src/types/ReferenceType.ts
@@ -1,12 +1,14 @@
 import type { GetTypeOptions, TypeChainEntry, TypeCompatibilityData } from '../interfaces';
 import type { GetSymbolTypeOptions, SymbolTypeGetterProvider } from '../SymbolTable';
 import type { SymbolTypeFlag } from '../SymbolTypeFlag';
-import { isAnyReferenceType, isArrayType, isComponentType, isDynamicType, isReferenceType } from '../astUtils/reflection';
+import { isAnyReferenceType, isArrayDefaultTypeReferenceType, isArrayType, isBinaryOperatorReferenceType, isComponentType, isDynamicType, isReferenceType, isTypePropertyReferenceType } from '../astUtils/reflection';
 import { BscType } from './BscType';
 import { DynamicType } from './DynamicType';
 import { BscTypeKind } from './BscTypeKind';
 import type { Token } from '../lexer/Token';
 
+export type AnyReferenceType = ReferenceType | TypePropertyReferenceType | BinaryOperatorReferenceType | ArrayDefaultTypeReferenceType;
+
 export function referenceTypeFactory(memberKey: string, fullName, flags: SymbolTypeFlag, tableProvider: SymbolTypeGetterProvider) {
     return new ReferenceType(memberKey, fullName, flags, tableProvider);
 }
@@ -330,6 +332,10 @@ export class TypePropertyReferenceType extends BscType {
                     return { name: 'TypePropertyReferenceType' };
                 }
 
+                if (propName === 'outerType') {
+                    return outerType;
+                }
+
                 if (isAnyReferenceType(this.outerType) && !this.outerType.isResolvable()) {
                     if (propName === 'getMemberType') {
                         //If we're calling `getMemberType()`, we need it to proxy to using the actual symbol table
@@ -399,6 +405,14 @@ export class BinaryOperatorReferenceType extends BscType {
                     return { name: 'BinaryOperatorReferenceType' };
                 }
 
+                if (propName === 'leftType') {
+                    return leftType;
+                }
+
+                if (propName === 'rightType') {
+                    return rightType;
+                }
+
                 let resultType: BscType = this.cachedType ?? DynamicType.instance;
                 if (!this.cachedType) {
                     if ((isAnyReferenceType(this.leftType) && !this.leftType.isResolvable()) ||
@@ -443,6 +457,10 @@ export class ArrayDefaultTypeReferenceType extends BscType {
                     return { name: 'ArrayDefaultTypeReferenceType' };
                 }
 
+                if (propName === 'objType') {
+                    return objType;
+                }
+
                 let resultType: BscType = this.cachedType ?? DynamicType.instance;
                 if (!this.cachedType) {
                     if ((isAnyReferenceType(this.objType) && !this.objType.isResolvable())
@@ -471,3 +489,46 @@ export class ArrayDefaultTypeReferenceType extends BscType {
         });
     }
 }
+
+/**
+ * Gives an array of all the symbol names that need to be resolved to make the given reference type be resolved
+ */
+export function getAllRequiredSymbolNames(refType: BscType, namespaceLower?: string): Array<{ name: string; namespacedName?: string }> {
+    if (refType.isResolvable()) {
+        return [];
+    }
+    if (isReferenceType(refType)) {
+        return [{ name: refType.fullName, namespacedName: namespaceLower ? `${namespaceLower}.${refType.fullName}` : null }];
+    }
+    if (isTypePropertyReferenceType(refType)) {
+        const outer = refType.outerType;
+        if (isAnyReferenceType(outer)) {
+            return getAllRequiredSymbolNames(outer);
+        } else {
+            return [];
+        }
+
+    }
+    if (isArrayDefaultTypeReferenceType(refType)) {
+        const objType = refType.objType;
+        if (isAnyReferenceType(objType)) {
+            return getAllRequiredSymbolNames(objType);
+        } else {
+            return [];
+        }
+    }
+    if (isBinaryOperatorReferenceType(refType)) {
+        const left = refType.leftType;
+        const right = refType.rightType;
+        const result = [];
+        if (isAnyReferenceType(left)) {
+            result.push(...getAllRequiredSymbolNames(left, namespaceLower));
+        }
+        if (isAnyReferenceType(right)) {
+            result.push(...getAllRequiredSymbolNames(right, namespaceLower));
+
+        }
+        return result;
+    }
+    return [];
+}
diff --git a/src/types/UnionType.ts b/src/types/UnionType.ts
index 8b32f04e..dc1e329f 100644
--- a/src/types/UnionType.ts
+++ b/src/types/UnionType.ts
@@ -119,7 +119,7 @@ export class UnionType extends BscType {
         for (const symbol of firstType.getMemberTable().getAllSymbols(SymbolTypeFlag.runtime)) {
             const foundType = this.getMemberTypeFromInnerTypes(symbol.name, { flags: SymbolTypeFlag.runtime });
             const allResolvableTypes = foundType.reduce((acc, curType) => {
-                return acc && curType.isResolvable();
+                return acc && curType?.isResolvable();
             }, true);
 
             if (!allResolvableTypes) {
diff --git a/src/types/helpers.ts b/src/types/helpers.ts
index d2d14fd2..be93ba3b 100644
--- a/src/types/helpers.ts
+++ b/src/types/helpers.ts
@@ -1,6 +1,7 @@
 import type { TypeCompatibilityData } from '../interfaces';
 import { isAnyReferenceType, isDynamicType, isEnumMemberType, isEnumType, isInheritableType, isInterfaceType, isUnionType } from '../astUtils/reflection';
 import type { BscType } from './BscType';
+import type { UnionType } from './UnionType';
 
 export function findTypeIntersection(typesArr1: BscType[], typesArr2: BscType[]) {
     if (!typesArr1 || !typesArr2) {
@@ -180,3 +181,16 @@ export function isNativeInterfaceCompatibleNumber(thisType: BscType, otherType:
     }
     return false;
 }
+
+export function getAllTypesFromUnionType(union: UnionType): BscType[] {
+    const results = [];
+
+    for (const type of union.types) {
+        if (isUnionType(type)) {
+            results.push(...getAllTypesFromUnionType(type));
+        } else {
+            results.push(type);
+        }
+    }
+    return results;
+}
diff --git a/src/util.ts b/src/util.ts
index 49bf1dd7..6f87b7f5 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -26,7 +26,7 @@ import type { CallExpression, CallfuncExpression, DottedGetExpression, FunctionP
 import { LogLevel, createLogger } from './logging';
 import { isToken, type Identifier, type Locatable, type Token } from './lexer/Token';
 import { TokenKind } from './lexer/TokenKind';
-import { isAnyReferenceType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallableType, isCallfuncExpression, isClassType, isDottedGetExpression, isDoubleType, isDynamicType, isEnumMemberType, isExpression, isFloatType, isIndexedGetExpression, isInvalidType, isLiteralString, isLongIntegerType, isNewExpression, isNumberType, isStringType, isTypeExpression, isTypedArrayExpression, isTypedFunctionType, isUnionType, isVariableExpression, isXmlAttributeGetExpression, isXmlFile } from './astUtils/reflection';
+import { isAnyReferenceType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallableType, isCallfuncExpression, isClassType, isDottedGetExpression, isDoubleType, isDynamicType, isEnumMemberType, isExpression, isFloatType, isIndexedGetExpression, isInvalidType, isLiteralString, isLongIntegerType, isNamespaceType, isNewExpression, isNumberType, isStringType, isTypeExpression, isTypedArrayExpression, isTypedFunctionType, isUnionType, isVariableExpression, isXmlAttributeGetExpression, isXmlFile } from './astUtils/reflection';
 import { WalkMode } from './astUtils/visitors';
 import { SourceNode } from 'source-map';
 import * as requireRelative from 'require-relative';
@@ -2026,6 +2026,7 @@ export class Util {
                 parentTypeName = previousTypeName;
                 parentTypeKind = itemTypeKind;
                 fullErrorName = previousTypeName ? `${previousTypeName}${dotSep}${chainItem.name}` : chainItem.name;
+                itemTypeKind = (chainItem.type as any)?.kind;
 
                 let typeString = chainItem.type?.toString();
                 let typeToFindStringFor = chainItem.type;
@@ -2040,6 +2041,15 @@ export class Util {
                             typeString = 'function';
                             break;
                         }
+                        parentTypeName = previousTypeName;
+                    } else if (isNamespaceType(typeToFindStringFor) && parentTypeName) {
+                        const chainItemTypeName = typeToFindStringFor.toString();
+                        typeString = parentTypeName + '.' + chainItemTypeName;
+                        if (chainItemTypeName.toLowerCase().startsWith(parentTypeName.toLowerCase())) {
+                            // the following namespace already knows...
+                            typeString = chainItemTypeName;
+                        }
+                        break;
                     } else {
                         typeString = typeToFindStringFor?.toString();
                         break;
@@ -2047,7 +2057,6 @@ export class Util {
                 }
 
                 previousTypeName = typeString ?? '';
-                itemTypeKind = (chainItem.type as any)?.kind;
                 itemName = chainItem.name;
                 astNode = chainItem.astNode;
                 containsDynamic = containsDynamic || (isDynamicType(chainItem.type) && !isAnyReferenceType(chainItem.type));
@@ -2089,28 +2098,31 @@ export class Util {
         return false;
     }
 
-    public setContainsUnresolvedSymbol(symbolLowerNameSet: Set<string>, symbol: UnresolvedSymbol) {
-        const possibleOriginalSymbolNamesLower = [];
-        let nameSoFar = '';
-        for (const tce of symbol.typeChain) {
-            if (nameSoFar.length > 0) {
-                nameSoFar += '.';
+    public hasAnyRequiredSymbolChanged(requiredSymbols: UnresolvedSymbol[], changedSymbols: Map<SymbolTypeFlag, Set<string>>) {
+        if (!requiredSymbols || !changedSymbols) {
+            return false;
+        }
+        const runTimeChanges = changedSymbols.get(SymbolTypeFlag.runtime);
+        const typeTimeChanges = changedSymbols.get(SymbolTypeFlag.typetime);
+
+        for (const symbol of requiredSymbols) {
+            if (this.setContainsUnresolvedSymbol(runTimeChanges, symbol) || this.setContainsUnresolvedSymbol(typeTimeChanges, symbol)) {
+                return true;
             }
-            nameSoFar += tce.name.toLowerCase();
-            possibleOriginalSymbolNamesLower.push(nameSoFar);
         }
-        const possibleNamespace = symbol.containingNamespaces?.join('.') ?? '';
 
-        for (const possibleNameLower of possibleOriginalSymbolNamesLower) {
+        return false;
+    }
+
+    public setContainsUnresolvedSymbol(symbolLowerNameSet: Set<string>, symbol: UnresolvedSymbol) {
+        if (!symbolLowerNameSet || symbolLowerNameSet.size === 0) {
+            return false;
+        }
+
+        for (const possibleNameLower of symbol.lookups) {
             if (symbolLowerNameSet.has(possibleNameLower)) {
                 return true;
             }
-            if (possibleNamespace) {
-                const fullName = possibleNamespace + '.' + possibleNameLower;
-                if (symbolLowerNameSet.has(fullName.toLowerCase())) {
-                    return true;
-                }
-            }
         }
         return false;
     }


Test patch:
diff --git a/src/testHelpers.spec.ts b/src/testHelpers.spec.ts
index 4865c3c2..fdebd84a 100644
--- a/src/testHelpers.spec.ts
+++ b/src/testHelpers.spec.ts
@@ -152,19 +152,19 @@ export function expectDiagnosticsIncludes(arg: DiagnosticCollection, expected: P
             return result as unknown as BsDiagnostic;
         });
 
-    let expectedFound = 0;
 
+    const foundDiagnostics: PartialDiagnostic | string | number | Array<PartialDiagnostic | string | number> = [];
     for (const expectedDiagnostic of expectedDiagnostics) {
-        const foundDiag = actualDiagnostics.find((actualDiag) => {
+        actualDiagnostics.find((actualDiag) => {
             const actualDiagnosticClone = cloneDiagnostic(actualDiag, expectedDiagnostic);
-            return JSON.stringify(actualDiagnosticClone) === JSON.stringify(expectedDiagnostic);
+            if (JSON.stringify(actualDiagnosticClone) === JSON.stringify(expectedDiagnostic)) {
+                foundDiagnostics.push(actualDiagnosticClone);
+                return true;
+            }
+            return false;
         });
-        if (foundDiag) {
-            expectedFound++;
-        }
-
     }
-    expect(expectedFound).to.eql(expectedDiagnostics.length);
+    expect(foundDiagnostics).to.eql(expectedDiagnostics);
 }
 
 /**