Repository: rokucommunity/brighterscript
Pull request #944 (2023-11-15 16:42:55)

Validation Performance: File level `providedSymbols` and `requiredSymbols`
```
benchmark: Running benchmarks:

    validate@local   --------- 271.514 ops/sec
    validate@0.65.10 --------- 101.219 ops/sec
```

⚡ Huge improvement to validation times! ⚡ 

How it works:

- When a file changes:
  - the file is broken down into individually-validatable segments (eg. each function statement, etc.)
  - each segment is indexed for symbols it requires to compile
  - all the symbols that are available externally to the file are indexed.
  - all the symbols that file requires are indexed (this is a Set of all the required symbols for all its segments)
  - all the symbols that have incompatible types to the previously-known types are marked especially.
- When there's a program validation
  - for the files that changed, check to see:
    - if there are any "duplicate symbols" in the same scope 
    - if the required symbols are are compatible for each scope the file is in
    - if there's a required symbol that is not in any scope
  - do a scope validation, but also pass along a reference to files that have changed, and what the changes are
- When validating a scope:
  - Mark each diagnostic if it was generated for a full scope validation vs. a validation based on an AST segment
  - clear any previously generated diagnostics if they were on a full scope validation
  - for each file in the scope
     -  validate the entire file if it's changed - but only once!
     - if it hasn't changed, and it does not require any symbols that have changed since last validation, do not validate!
     - if it does require something that has changed, than only for the segments in the file that require that symbol, validate the segment.
     - do appropriate clearing of diagnostics attached to the scope based on file and segment re-validations.
 

Known issues:
~The language server still weirdly re-validates for all open files, and there seems to be a race condition somewhere. I see it most notably on Brighterscript files, and when using classes/types defined in namespaces~** these are fixed!**


rokucommunity-bot[bot]: Hey there! I just built a new temporary npm package based on 48f614c07ba5ff04a0ad4c9fec9b8e7c68946ddc. You can download it [here](https://github.com/rokucommunity/brighterscript/releases/download/v0.0.0-packages/brighterscript-0.66.0-alpha.7-providers-and-requires.20231117211721.tgz) or install it by running the following command: 
```bash
npm install https://github.com/rokucommunity/brighterscript/releases/download/v0.0.0-packages/brighterscript-0.66.0-alpha.7-providers-and-requires.20231117211721.tgz
```
rokucommunity-bot[bot]: Hey there! I just built a new temporary npm package based on 1a3fa631c962c219d107298389cd915db3d1f361. You can download it [here](https://github.com/rokucommunity/brighterscript/releases/download/v0.0.0-packages/brighterscript-0.66.0-alpha.7-providers-and-requires.20231118025148.tgz) or install it by running the following command: 
```bash
npm install https://github.com/rokucommunity/brighterscript/releases/download/v0.0.0-packages/brighterscript-0.66.0-alpha.7-providers-and-requires.20231118025148.tgz
```

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 658fdd2a..174b260f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -15,7 +15,7 @@ jobs:
       COVERALLS_REPO_TOKEN: "kVPcGJfehOArEQnHb8BT3KS72xbUQCoxI"
     strategy:
       matrix:
-        os: [ubuntu-latest, macos-latest, windows-latest]
+        os: [macos-latest, ubuntu-latest, windows-latest]
     steps:
       - uses: actions/checkout@master
       - uses: actions/setup-node@master
diff --git a/.github/workflows/create-package.yml b/.github/workflows/create-package.yml
index 6605f6df..196c3a38 100644
--- a/.github/workflows/create-package.yml
+++ b/.github/workflows/create-package.yml
@@ -34,6 +34,8 @@ jobs:
           echo "ARTIFACT_URL=$ARTIFACT_URL" >> $GITHUB_ENV
 
       - run: npm ci
+      - run: npm config set ignore-scripts true
+      - run: npm run build
       - run: npm version "$BUILD_VERSION" --no-git-tag-version
       - run: npm pack
 
diff --git a/benchmarks/package-lock.json b/benchmarks/package-lock.json
index dd52b46e..ca357cdd 100644
--- a/benchmarks/package-lock.json
+++ b/benchmarks/package-lock.json
@@ -10,8 +10,8 @@
             "license": "MIT",
             "dependencies": {
                 "benchmark": "^2.1.4",
-                "brighterscript1": "file:.tmp/brighterscript-0.66.0-alpha.71698945001324.tgz",
-                "brighterscript2": "npm:brighterscript@^0.65.8",
+                "brighterscript1": "file:.tmp/brighterscript-0.66.0-alpha.71700251305166.tgz",
+                "brighterscript2": "npm:brighterscript@^0.65.10",
                 "chalk": "^4.1.2",
                 "fast-glob": "^3.2.12",
                 "fs-extra": "^11.1.1",
@@ -608,8 +608,8 @@
         "node_modules/brighterscript1": {
             "name": "brighterscript",
             "version": "0.66.0-alpha.7",
-            "resolved": "file:.tmp/brighterscript-0.66.0-alpha.71698945001324.tgz",
-            "integrity": "sha512-m7voOWy/ZlrVjeH6MrFLRXBw2uF6Qy6/9of+ehjV4W6zY4H/+henar+F60jgxgT+Kn518ogTHvaWoTRZAGUPPw==",
+            "resolved": "file:.tmp/brighterscript-0.66.0-alpha.71700251305166.tgz",
+            "integrity": "sha512-uXFzXBIqBbIJMY/ynyje8DhBwKLnnrO50FQNji/eKESeiYAXnJUJXjG1P2YIC/5trfm+fogWSqH+UiVOs4/8QQ==",
             "license": "MIT",
             "dependencies": {
                 "@rokucommunity/bslib": "^0.1.1",
@@ -773,9 +773,9 @@
         },
         "node_modules/brighterscript2": {
             "name": "brighterscript",
-            "version": "0.65.8",
-            "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.8.tgz",
-            "integrity": "sha512-mqarSnGCl/VSTRj1oY9wRMZJV9s8dH+rZZ2P1yE7d1nZQucVpAIGICW83G9AJHQYUDjrGsKHn5UfAfaZaE1KCA==",
+            "version": "0.65.10",
+            "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.10.tgz",
+            "integrity": "sha512-JGBkH6VDgTfwTMHtvauwUeR8XAotuhURbtr3r9+zaWU/KyO0jwSCLUnoNUjSmsz9ddnWe6KOY3NpR5vVG/iraA==",
             "dependencies": {
                 "@rokucommunity/bslib": "^0.1.1",
                 "@xml-tools/parser": "^1.0.7",
@@ -800,7 +800,7 @@
                 "parse-ms": "^2.1.0",
                 "readline": "^1.3.0",
                 "require-relative": "^0.8.7",
-                "roku-deploy": "^3.10.3",
+                "roku-deploy": "^3.10.5",
                 "serialize-error": "^7.0.1",
                 "source-map": "^0.7.4",
                 "vscode-languageserver": "7.0.0",
@@ -2872,9 +2872,9 @@
             }
         },
         "node_modules/roku-deploy": {
-            "version": "3.10.3",
-            "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.3.tgz",
-            "integrity": "sha512-COJSQ638QklcM+8AN1nujFuzT04rTZLFuLSww35edm8w/y0l60oF/Iu7TQ46m75DwoGFzGFfomLEmA1ltQk9mA==",
+            "version": "3.10.5",
+            "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.5.tgz",
+            "integrity": "sha512-acSi9LKKE8cC1mXul2wNiw7++dxNEWJJCVBS9FJQY7LYaYQ4kQVnWuptjrZ5W8Mu8xhvhytdMVZZhoMCTUoKvQ==",
             "dependencies": {
                 "chalk": "^2.4.2",
                 "dateformat": "^3.0.3",
@@ -2884,6 +2884,7 @@
                 "is-glob": "^4.0.3",
                 "jsonc-parser": "^2.3.0",
                 "jszip": "^3.6.0",
+                "lodash": "^4.17.21",
                 "micromatch": "^4.0.4",
                 "moment": "^2.29.1",
                 "parse-ms": "^2.1.0",
@@ -4089,8 +4090,8 @@
             }
         },
         "brighterscript1": {
-            "version": "file:.tmp/brighterscript-0.66.0-alpha.71698945001324.tgz",
-            "integrity": "sha512-m7voOWy/ZlrVjeH6MrFLRXBw2uF6Qy6/9of+ehjV4W6zY4H/+henar+F60jgxgT+Kn518ogTHvaWoTRZAGUPPw==",
+            "version": "file:.tmp/brighterscript-0.66.0-alpha.71700251305166.tgz",
+            "integrity": "sha512-uXFzXBIqBbIJMY/ynyje8DhBwKLnnrO50FQNji/eKESeiYAXnJUJXjG1P2YIC/5trfm+fogWSqH+UiVOs4/8QQ==",
             "requires": {
                 "@rokucommunity/bslib": "^0.1.1",
                 "@xml-tools/parser": "^1.0.7",
@@ -4227,9 +4228,9 @@
             }
         },
         "brighterscript2": {
-            "version": "npm:brighterscript@0.65.8",
-            "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.8.tgz",
-            "integrity": "sha512-mqarSnGCl/VSTRj1oY9wRMZJV9s8dH+rZZ2P1yE7d1nZQucVpAIGICW83G9AJHQYUDjrGsKHn5UfAfaZaE1KCA==",
+            "version": "npm:brighterscript@0.65.10",
+            "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.10.tgz",
+            "integrity": "sha512-JGBkH6VDgTfwTMHtvauwUeR8XAotuhURbtr3r9+zaWU/KyO0jwSCLUnoNUjSmsz9ddnWe6KOY3NpR5vVG/iraA==",
             "requires": {
                 "@rokucommunity/bslib": "^0.1.1",
                 "@xml-tools/parser": "^1.0.7",
@@ -4254,7 +4255,7 @@
                 "parse-ms": "^2.1.0",
                 "readline": "^1.3.0",
                 "require-relative": "^0.8.7",
-                "roku-deploy": "^3.10.3",
+                "roku-deploy": "^3.10.5",
                 "serialize-error": "^7.0.1",
                 "source-map": "^0.7.4",
                 "vscode-languageserver": "7.0.0",
@@ -5833,9 +5834,9 @@
             }
         },
         "roku-deploy": {
-            "version": "3.10.3",
-            "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.3.tgz",
-            "integrity": "sha512-COJSQ638QklcM+8AN1nujFuzT04rTZLFuLSww35edm8w/y0l60oF/Iu7TQ46m75DwoGFzGFfomLEmA1ltQk9mA==",
+            "version": "3.10.5",
+            "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.5.tgz",
+            "integrity": "sha512-acSi9LKKE8cC1mXul2wNiw7++dxNEWJJCVBS9FJQY7LYaYQ4kQVnWuptjrZ5W8Mu8xhvhytdMVZZhoMCTUoKvQ==",
             "requires": {
                 "chalk": "^2.4.2",
                 "dateformat": "^3.0.3",
@@ -5845,6 +5846,7 @@
                 "is-glob": "^4.0.3",
                 "jsonc-parser": "^2.3.0",
                 "jszip": "^3.6.0",
+                "lodash": "^4.17.21",
                 "micromatch": "^4.0.4",
                 "moment": "^2.29.1",
                 "parse-ms": "^2.1.0",
diff --git a/src/AstValidationSegmenter.ts b/src/AstValidationSegmenter.ts
index 8b463772..e7177606 100644
--- a/src/AstValidationSegmenter.ts
+++ b/src/AstValidationSegmenter.ts
@@ -1,28 +1,34 @@
-import type { DottedGetExpression, TypeExpression, VariableExpression } from '.';
+import type { DottedGetExpression, TypeExpression, VariableExpression } from './parser/Expression';
 import { SymbolTypeFlag } from './SymbolTable';
-import { UnresolvedNodeSet } from './UnresolvedNodeSet';
-import { isBody, isClassStatement, isCommentStatement, isInterfaceStatement, isNamespaceStatement } from './astUtils/reflection';
+import { isBody, isClassStatement, isCommentStatement, isInterfaceStatement, isNamespaceStatement, isVariableExpression } from './astUtils/reflection';
 import { ChildrenSkipper, WalkMode, createVisitor } from './astUtils/visitors';
-import type { ExtraSymbolData, GetTypeOptions } from './interfaces';
+import type { GetTypeOptions, TypeChainEntry } from './interfaces';
 import type { AstNode } from './parser/AstNode';
 import { util } from './util';
+import type { NamespaceStatement } from '.';
 
 // eslint-disable-next-line no-bitwise
 export const InsideSegmentWalkMode = WalkMode.visitStatements | WalkMode.visitExpressions | WalkMode.recurseChildFunctions;
 
+export interface UnresolvedSymbol {
+    typeChain: TypeChainEntry[];
+    flags: SymbolTypeFlag;
+    endChainFlags: SymbolTypeFlag;
+    containingNamespaces: string[];
+}
+
 export class AstValidationSegmenter {
 
-    public unresolvedSegments = new Map<AstNode, UnresolvedNodeSet>();
     public validatedSegments = new Map<AstNode, boolean>();
     public segmentsForValidation = new Array<AstNode>();
     public singleValidationSegments = new Set<AstNode>();
+    public unresolvedSegmentsSymbols = new Map<AstNode, Set<UnresolvedSymbol>>();
     public ast: AstNode;
 
-
     reset() {
-        this.unresolvedSegments.clear();
         this.validatedSegments.clear();
         this.singleValidationSegments.clear();
+        this.unresolvedSegmentsSymbols.clear();
         this.segmentsForValidation = [];
     }
 
@@ -36,30 +42,39 @@ export class AstValidationSegmenter {
         });
     }
 
-
-    checkExpressionForUnresolved(segment: AstNode, expression: VariableExpression | DottedGetExpression | TypeExpression) {
+    checkExpressionForUnresolved(segment: AstNode, expression: VariableExpression | DottedGetExpression | TypeExpression, assignedSymbols?: Set<string>) {
         if (!expression || isCommentStatement(expression)) {
             return false;
         }
+        if (isVariableExpression(expression) && expression.name.text.toLowerCase() === 'm') {
+            return false;
+        }
         const flag = util.isInTypeExpression(expression) ? SymbolTypeFlag.typetime : SymbolTypeFlag.runtime;
-        const options: GetTypeOptions = { flags: flag, onlyCacheResolvedTypes: true };
+        const typeChain: TypeChainEntry[] = [];
+        const options: GetTypeOptions = { flags: flag, onlyCacheResolvedTypes: true, typeChain: typeChain };
+
         const nodeType = expression.getType(options);
         if (!nodeType.isResolvable()) {
-            let nodeSet: UnresolvedNodeSet;
-            if (!this.unresolvedSegments.has(segment)) {
-                nodeSet = new UnresolvedNodeSet(segment);
-                this.unresolvedSegments.set(segment, nodeSet);
-            } else {
-                nodeSet = this.unresolvedSegments.get(segment);
+            let symbolsSet: Set<UnresolvedSymbol>;
+            if (!assignedSymbols?.has(typeChain[0].name.toLowerCase())) {
+                if (!this.unresolvedSegmentsSymbols.has(segment)) {
+                    symbolsSet = new Set<UnresolvedSymbol>();
+                    this.unresolvedSegmentsSymbols.set(segment, symbolsSet);
+                } else {
+                    symbolsSet = this.unresolvedSegmentsSymbols.get(segment);
+                }
+
+                symbolsSet.add({ typeChain: typeChain, flags: typeChain[0].flags, endChainFlags: flag, containingNamespaces: this.currentNamespaceStatement?.getNameParts()?.map(t => t.text) });
             }
-            nodeSet.addExpression(expression, options);
             return true;
         }
         return false;
     }
 
-    checkSegmentWalk = (segment: AstNode) => {
-        if (isNamespaceStatement(segment) || isBody(segment)) {
+    private currentNamespaceStatement: NamespaceStatement;
+
+    checkSegmentWalk(segment: AstNode) {
+        if (isNamespaceStatement(segment) || isBody(segment) || isCommentStatement(segment)) {
             return;
         }
         if (isClassStatement(segment)) {
@@ -89,19 +104,30 @@ export class AstValidationSegmenter {
         this.validatedSegments.set(segment, false);
         let foundUnresolvedInSegment = false;
         const skipper = new ChildrenSkipper();
+        const assignedSymbols = new Set<string>();
+        this.currentNamespaceStatement = segment.findAncestor(isNamespaceStatement);
+
         segment.walk(createVisitor({
+            AssignmentStatement: (stmt) => {
+                assignedSymbols.add(stmt.name.text.toLowerCase());
+            },
+            FunctionParameterExpression: (expr) => {
+                assignedSymbols.add(expr.name.text.toLowerCase());
+            },
             VariableExpression: (expr) => {
-                const expressionIsUnresolved = this.checkExpressionForUnresolved(segment, expr);
-                foundUnresolvedInSegment = expressionIsUnresolved || foundUnresolvedInSegment;
+                if (!assignedSymbols.has(expr.name.text.toLowerCase())) {
+                    const expressionIsUnresolved = this.checkExpressionForUnresolved(segment, expr, assignedSymbols);
+                    foundUnresolvedInSegment = expressionIsUnresolved || foundUnresolvedInSegment;
+                }
                 skipper.skip();
             },
             DottedGetExpression: (expr) => {
-                const expressionIsUnresolved = this.checkExpressionForUnresolved(segment, expr);
+                const expressionIsUnresolved = this.checkExpressionForUnresolved(segment, expr, assignedSymbols);
                 foundUnresolvedInSegment = expressionIsUnresolved || foundUnresolvedInSegment;
                 skipper.skip();
             },
             TypeExpression: (expr) => {
-                const expressionIsUnresolved = this.checkExpressionForUnresolved(segment, expr);
+                const expressionIsUnresolved = this.checkExpressionForUnresolved(segment, expr, assignedSymbols);
                 foundUnresolvedInSegment = expressionIsUnresolved || foundUnresolvedInSegment;
                 skipper.skip();
             }
@@ -112,34 +138,26 @@ export class AstValidationSegmenter {
         if (!foundUnresolvedInSegment) {
             this.singleValidationSegments.add(segment);
         }
-    };
+    }
 
 
-    getSegments(): AstNode[] {
+    getSegments(changedSymbols: Map<SymbolTypeFlag, Set<string>>): AstNode[] {
         const segmentsToWalkForValidation: AstNode[] = [];
         for (const segment of this.segmentsForValidation) {
-            const unresolvedNodeSet = this.unresolvedSegments.get(segment);
+            const symbolsRequired = this.unresolvedSegmentsSymbols.get(segment);
+
             const isSingleValidationSegment = this.singleValidationSegments.has(segment);
             const singleValidationSegmentAlreadyValidated = isSingleValidationSegment ? this.validatedSegments.get(segment) : false;
             let segmentNeedsRevalidation = !singleValidationSegmentAlreadyValidated;
-            if (unresolvedNodeSet) {
-                for (let node of unresolvedNodeSet.nodes) {
-                    const data: ExtraSymbolData = {};
-                    const options: GetTypeOptions = { flags: util.isInTypeExpression(node) ? SymbolTypeFlag.typetime : SymbolTypeFlag.runtime, data: data };
-                    const type = node.getType(options);
-                    if (!type || !type.isResolvable()) {
-                        // the type that we're checking here is not found - force validation
-                        segmentNeedsRevalidation = true;
+
+            if (symbolsRequired) {
+                for (const requiredSymbol of symbolsRequired.values()) {
+                    const changeSymbolSetForFlag = changedSymbols.get(requiredSymbol.flags);
+                    if (util.setContainsUnresolvedSymbol(changeSymbolSetForFlag, requiredSymbol)) {
+                        segmentsToWalkForValidation.push(segment);
                         break;
-                    } else {
-                        const newTypeToCheck = unresolvedNodeSet.addTypeForExpression(node, options, type);
-                        segmentNeedsRevalidation = segmentNeedsRevalidation || newTypeToCheck;
                     }
                 }
-                if (segmentNeedsRevalidation) {
-                    segmentsToWalkForValidation.push(segment);
-                    continue;
-                }
             } else if (segmentNeedsRevalidation) {
                 segmentsToWalkForValidation.push(segment);
             }
diff --git a/src/DiagnosticMessages.ts b/src/DiagnosticMessages.ts
index a8579d02..124f769f 100644
--- a/src/DiagnosticMessages.ts
+++ b/src/DiagnosticMessages.ts
@@ -753,6 +753,11 @@ export let DiagnosticMessages = {
         message: `Operator '${operatorString}' cannot be applied to type${secondType ? 's' : ''} '${firstType}'${secondType ? ` and '${secondType}'` : ''}`,
         code: 1144,
         severity: DiagnosticSeverity.Error
+    }),
+    incompatibleSymbolDefinition: (symbol: string, scopeName: string) => ({
+        message: `'${symbol}' is incompatible across scope group (${scopeName})`, // TODO: Add scopes where it was defined
+        code: 1145,
+        severity: DiagnosticSeverity.Error
     })
 };
 export const defaultMaximumTruncationLength = 160;
diff --git a/src/LanguageServer.ts b/src/LanguageServer.ts
index 7cc195be..112bcc1e 100644
--- a/src/LanguageServer.ts
+++ b/src/LanguageServer.ts
@@ -1133,7 +1133,6 @@ export class LanguageServer {
             await this.synchronizeStandaloneProjects();
 
             let projects = this.getProjects();
-
             //validate all programs
             await Promise.all(
                 projects.map((project) => {
@@ -1141,6 +1140,7 @@ export class LanguageServer {
                     return project;
                 })
             );
+
         } catch (e: any) {
             this.connection.console.error(e);
             this.sendCriticalFailure(`Critical error validating project: ${e.message}${e.stack ?? ''}`);
@@ -1267,6 +1267,8 @@ export class LanguageServer {
         await this.waitAllProjectFirstRuns();
         //wait for the file to settle (in case there are multiple file changes in quick succession)
         await this.keyedThrottler.onIdleOnce(util.uriToPath(params.textDocument.uri), true);
+        // make sure validation is complete
+        await this.validateAllThrottled();
         //wait for the validation cycle to settle
         await this.onValidateSettled();
 
diff --git a/src/Program.ts b/src/Program.ts
index b4a0db98..a203c63c 100644
--- a/src/Program.ts
+++ b/src/Program.ts
@@ -5,7 +5,7 @@ import type { CodeAction, Position, Range, SignatureInformation, Location } from
 import type { BsConfig } from './BsConfig';
 import { Scope } from './Scope';
 import { DiagnosticMessages } from './DiagnosticMessages';
-import type { BrsFile } from './files/BrsFile';
+import type { BrsFile, ProvidedSymbolInfo } from './files/BrsFile';
 import type { XmlFile } from './files/XmlFile';
 import type { BsDiagnostic, FileObj, SemanticToken, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, BeforeFileAddEvent, BeforeFileRemoveEvent, PrepareFileEvent, PrepareProgramEvent, ProvideFileEvent, SerializedFile, TranspileObj } from './interfaces';
 import type { File } from './files/File';
@@ -46,8 +46,10 @@ 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';
 import { InterfaceType } from './types';
 import { BuiltInInterfaceAdder } from './types/BuiltInInterfaceAdder';
+import type { UnresolvedSymbol } from './AstValidationSegmenter';
 
 const bslibNonAliasedRokuModulesPkgPath = s`source/roku_modules/rokucommunity_bslib/bslib.brs`;
 const bslibAliasedRokuModulesPkgPath = s`source/roku_modules/bslib/bslib.brs`;
@@ -104,6 +106,7 @@ export class Program {
 
         //hardcode the files list for global scope to only contain the global file
         this.globalScope.getAllFiles = () => [globalFile];
+        globalFile.isValidated = true;
         this.globalScope.validate();
         //for now, disable validation of global scope because the global files have some duplicate method declarations
         this.globalScope.getDiagnostics = () => [];
@@ -212,6 +215,20 @@ export class Program {
      */
     private diagnostics = [] as BsDiagnostic[];
 
+
+    private fileSymbolInformation = new Map<string, { provides: ProvidedSymbolInfo; requires: UnresolvedSymbol[] }>();
+
+    public addFileSymbolInfo(file: BrsFile) {
+        this.fileSymbolInformation.set(file.pkgPath, {
+            provides: file.providedSymbols,
+            requires: file.requiredSymbols
+        });
+    }
+
+    public getFileSymbolInfo(file: BrsFile) {
+        return this.fileSymbolInformation.get(file.pkgPath);
+    }
+
     /**
      * The path to bslib.brs (the BrightScript runtime for certain BrighterScript features)
      */
@@ -537,7 +554,7 @@ export class Program {
         let file = this.logger.time(LogLevel.debug, ['Program.setFile()', chalk.green(srcPath)], () => {
             //if the file is already loaded, remove it
             if (this.hasFile(srcPath)) {
-                this.removeFile(srcPath);
+                this.removeFile(srcPath, true, true);
             }
 
             const data = new LazyFileData(fileData);
@@ -740,7 +757,7 @@ export class Program {
      * @param filePath can be a srcPath, a destPath, or a destPath with leading `pkg:/`
      * @param normalizePath should this function repair and standardize the path? Passing false should have a performance boost if you can guarantee your path is already sanitized
      */
-    public removeFile(filePath: string, normalizePath = true) {
+    public removeFile(filePath: string, normalizePath = true, keepSymbolInformation = false) {
         this.logger.debug('Program.removeFile()', filePath);
         const paths = this.getPaths(filePath, this.options.rootDir);
 
@@ -779,6 +796,9 @@ 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 (!keepSymbolInformation) {
+                    this.fileSymbolInformation.delete(file.pkgPath);
+                }
             }
 
             //if this is a component, remove it from our components map
@@ -796,6 +816,14 @@ export class Program {
         }
     }
 
+
+    public lastValidationInfo = new Map<string, {
+        symbolsNotDefinedInEveryScope: { symbol: UnresolvedSymbol; scope: Scope }[];
+        duplicateSymbolsInSameScope: { symbol: UnresolvedSymbol; scope: Scope }[];
+        symbolsNotConsistentAcrossScopes: { symbol: UnresolvedSymbol; scopes: Scope[] }[];
+    }>();
+
+
     /**
      * Traverse the entire project, and validate all scopes
      */
@@ -809,6 +837,7 @@ export class Program {
             this.plugins.emit('onProgramValidate', programValidateEvent);
 
             //validate every file
+            const brsFilesValidated: BrsFile[] = [];
             for (const file of Object.values(this.files)) {
                 //for every unvalidated file, validate it
                 if (!file.isValidated) {
@@ -820,10 +849,84 @@ export class Program {
                     //emit an event to allow plugins to contribute to the file validation process
                     this.plugins.emit('onFileValidate', validateFileEvent);
                     file.isValidated = true;
+                    if (isBrsFile(file)) {
+                        brsFilesValidated.push(file);
+                    }
                     this.plugins.emit('afterFileValidate', validateFileEvent);
                 }
             }
 
+            // build list of all changed symbols in each file that changed
+            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;
+                                    } else {
+                                        //get more general type
+                                        if (providedSymbolType.isEqual(symbolInThisScope)) {
+                                            //type in this scope is the same as one we're already checking
+                                        } else if (providedSymbolType.isTypeCompatible(symbolInThisScope)) {
+                                            //type in this scope is compatible with one we're storing. use most generic
+                                            providedSymbolType = symbolInThisScope;
+                                        } else if (symbolInThisScope.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);
+            }
+
+            this.detectIncompatibleSymbolsAcrossScopes();
+
             // Build component types for any component that changes
             this.logger.time(LogLevel.info, ['Build component types'], () => {
                 for (let { componentKey, componentName } of this.componentSymbolsToUpdate) {
@@ -832,10 +935,24 @@ export class Program {
                 this.componentSymbolsToUpdate.clear();
             });
 
+
+            const changedSymbolsMapArr = brsFilesValidated?.map(f => {
+                if (isBrsFile(f)) {
+                    return f.providedSymbols.changes;
+                }
+                return null;
+            }).filter(x => x);
+
+            const changedSymbols = new Map<SymbolTypeFlag, Set<string>>();
+            for (const flag of [SymbolTypeFlag.runtime, SymbolTypeFlag.typetime]) {
+                const changedSymbolsSetArr = changedSymbolsMapArr.map(symMap => symMap.get(flag));
+                changedSymbols.set(flag, new Set(...changedSymbolsSetArr));
+            }
+
             this.logger.time(LogLevel.info, ['Validate all scopes'], () => {
                 for (let scopeName in this.scopes) {
                     let scope = this.scopes[scopeName];
-                    scope.validate();
+                    scope.validate({ changedFiles: brsFilesValidated, changedSymbols: changedSymbols });
                 }
             });
 
@@ -845,6 +962,22 @@ export class Program {
         });
     }
 
+
+    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.push({
+                    ...DiagnosticMessages.incompatibleSymbolDefinition(typeChainResult.fullNameOfItem, scopeListName),
+                    file: file,
+                    range: typeChainResult.range
+                });
+            }
+        }
+    }
+
     /**
      * Flag all duplicate component names
      */
diff --git a/src/Scope.spec.ts b/src/Scope.spec.ts
index c3c9b935..f2d2dffb 100644
--- a/src/Scope.spec.ts
+++ b/src/Scope.spec.ts
@@ -9,9 +9,10 @@ import { expectDiagnostics, expectDiagnosticsIncludes, expectTypeToBe, expectZer
 import { Logger } from './Logger';
 import type { BrsFile } from './files/BrsFile';
 import type { FunctionStatement, NamespaceStatement } from './parser/Statement';
-import type { OnScopeValidateEvent } from './interfaces';
+import type { CompilerPlugin, OnScopeValidateEvent } from './interfaces';
+import { DiagnosticOrigin } from './interfaces';
 import { SymbolTypeFlag } from './SymbolTable';
-import { EnumMemberType } from './types/EnumType';
+import { EnumMemberType, EnumType } from './types/EnumType';
 import { ClassType } from './types/ClassType';
 import { BooleanType } from './types/BooleanType';
 import { StringType } from './types/StringType';
@@ -25,8 +26,9 @@ import { UnionType } from './types/UnionType';
 import { isFunctionStatement, isNamespaceStatement } from './astUtils/reflection';
 import { ArrayType } from './types/ArrayType';
 import { AssociativeArrayType } from './types/AssociativeArrayType';
-import { InterfaceType } from '.';
+import { InterfaceType } from './types/InterfaceType';
 import { ComponentType } from './types/ComponentType';
+import * as path from 'path';
 
 describe('Scope', () => {
     let sinon = sinonImport.createSandbox();
@@ -171,7 +173,8 @@ describe('Scope', () => {
         const expected = [{
             message: 'message',
             file: undefined,
-            range: undefined
+            range: undefined,
+            origin: DiagnosticOrigin.Scope
         }];
         source.addDiagnostics(expected);
         expectDiagnostics(source, expected);
@@ -1925,7 +1928,7 @@ describe('Scope', () => {
                 `);
                 program.validate();
                 expectDiagnostics(program, [
-                    DiagnosticMessages.itemCannotBeUsedAsType('iFace.name').message
+                    DiagnosticMessages.cannotFindName('name').message
                 ]);
             });
 
@@ -3074,6 +3077,20 @@ describe('Scope', () => {
                 sourceScope.unlinkSymbolTable();
             });
         });
+
+        it('classes in namespaces that reference themselves without namespace work', () => {
+            program.setFile<BrsFile>('source/class.bs', `
+                namespace Alpha
+                    class TestClass
+                        function getCopy() as TestClass
+                            return new TestClass()
+                        end function
+                    end class
+                end namespace
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
     });
 
     describe('unlinkSymbolTable', () => {
@@ -3124,6 +3141,305 @@ 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
+                    function copy(otherKlass as Klass) as Klass
+                        return otherKlass
+                    end function
+                end class
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+        it('classes can reference each other across files', () => {
+            program.setFile<BrsFile>('source/klass.bs', `
+                class Klass
+                    function copy(otherKlass as Klass2) as Klass
+                        return otherKlass.getKlass()
+                    end function
+                end class
+            `);
+
+            program.setFile<BrsFile>('source/klass2.bs', `
+                class Klass2
+                    function getKlass() as Klass
+                        return new Klass()
+                    end function
+                end class
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+        });
+
+        it('requires a enum defined in a different namespace', () => {
+            let file1 = program.setFile<BrsFile>('source/file1.bs', `
+                namespace Alpha
+                    function printEnum(enumVal as Alpha.Beta.Charlie.SomeEnum) as string
+                        return enumVal.toStr()
+                    end function
+                end namespace
+            `);
+
+            let file2 = program.setFile<BrsFile>('source/file2.bs', `
+                namespace Alpha.Beta.Charlie
+                    enum SomeEnum
+                        val1 = 1
+                        val2 = 2
+                    end enum
+                end namespace
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+            expect(file1.requiredSymbols.length).to.eq(1);
+            expect(file1.requiredSymbols[0].containingNamespaces).to.have.members(['Alpha']);
+            expect(file1.requiredSymbols[0].flags).to.eq(SymbolTypeFlag.typetime);
+            expect(file1.requiredSymbols[0].typeChain.length).to.eq(4);
+            expect(file1.requiredSymbols[0].typeChain.map(x => x.name)).to.have.members(['Alpha', 'Beta', 'Charlie', 'SomeEnum']);
+            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'), EnumType);
+        });
+
+        it('classes that extend classes in other files show change properly', () => {
+            program.setFile<BrsFile>('source/class.bs', `
+                class TestClass
+                    name as string
+                end class
+            `);
+
+            program.setFile<BrsFile>('source/subclass.bs', `
+                class SubTestClass extends TestClass
+                    length as float
+                end class
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+
+            program.setFile<BrsFile>('source/subclass.bs', `
+                class SubTestClass extends TestClass
+                    ' added a comment
+                    length as float
+                end class
+            `);
+
+            let changedSymbolsSize = -1;
+            class TestScopeValidator implements CompilerPlugin {
+                name = 'TestScopeValidator';
+                public onScopeValidate(event: OnScopeValidateEvent) {
+                    changedSymbolsSize = event.changedSymbols.get(SymbolTypeFlag.runtime).size;
+                }
+            }
+
+            program.plugins.add(new TestScopeValidator());
+            program.validate();
+            expectZeroDiagnostics(program);
+            expect(changedSymbolsSize).to.equal(0);
+        });
+
+    });
+
     describe('performance', () => {
 
         // eslint-disable-next-line func-names, prefer-arrow-callback
diff --git a/src/Scope.ts b/src/Scope.ts
index c50589fb..a76e272e 100644
--- a/src/Scope.ts
+++ b/src/Scope.ts
@@ -4,7 +4,7 @@ import * as path from 'path';
 import chalk from 'chalk';
 import type { DiagnosticInfo } from './DiagnosticMessages';
 import { DiagnosticMessages } from './DiagnosticMessages';
-import type { CallableContainer, BsDiagnostic, FileReference, CallableContainerMap, FileLink, Callable, NamespaceContainer } from './interfaces';
+import { type CallableContainer, type BsDiagnosticWithOrigin, type FileReference, type CallableContainerMap, type FileLink, type Callable, type NamespaceContainer, type ScopeValidationOptions, type BsDiagnostic, DiagnosticOrigin } from './interfaces';
 import type { Program } from './Program';
 import { BsClassValidator } from './validators/ClassValidator';
 import type { NamespaceStatement, ClassStatement, EnumStatement, InterfaceStatement, EnumMemberStatement, ConstStatement } from './parser/Statement';
@@ -25,6 +25,7 @@ import { NamespaceType } from './types/NamespaceType';
 import { referenceTypeFactory } from './types/ReferenceType';
 import { unionTypeFactory } from './types/UnionType';
 import { AssociativeArrayType } from './types/AssociativeArrayType';
+import type { AstNode } from './parser/AstNode';
 
 /**
  * Assign some few factories to the SymbolTable to prevent cyclical imports. This file seems like the most intuitive place to do the linking
@@ -65,6 +66,21 @@ export class Scope {
      * "namea", "namea.nameb", "namea.nameb.namec"
      */
     public get namespaceLookup() {
+        let allFilesValidated = true;
+        for (const file of this.getAllFiles()) {
+            if (isBrsFile(file) && !file.hasTypedef) {
+                allFilesValidated = allFilesValidated && file.isValidated;
+                if (!allFilesValidated) {
+                    break;
+                }
+            }
+        }
+        if (!allFilesValidated) {
+            // This is not fit to cache
+            // Since the files have not been validated, all namespace info might not have been available
+            return this.buildNamespaceLookup();
+        }
+
         return this.cache.getOrAdd('namespaceLookup', () => this.buildNamespaceLookup());
     }
 
@@ -344,7 +360,7 @@ export class Scope {
     /**
      * The list of diagnostics found specifically for this scope. Individual file diagnostics are stored on the files themselves.
      */
-    protected diagnostics = [] as BsDiagnostic[];
+    protected diagnostics = [] as BsDiagnosticWithOrigin[];
 
     protected onDependenciesChanged(event: DependencyChangedEvent) {
         this.logDebug('invalidated because dependency graph said [', event.sourceKey, '] changed');
@@ -483,14 +499,14 @@ export class Scope {
             //get diagnostics from all files
             ...this.getOwnFiles().map(x => x.diagnostics ?? []).flat()
         ]
-            //exclude diangostics that match any of the comment flags
+            //exclude diagnostics that match any of the comment flags
             .filter((x) => {
                 return !util.diagnosticIsSuppressed(x);
             });
         return diagnostics;
     }
 
-    public addDiagnostics(diagnostics: BsDiagnostic[]) {
+    public addDiagnostics(diagnostics: BsDiagnosticWithOrigin[]) {
         this.diagnostics.push(...diagnostics);
     }
 
@@ -584,10 +600,11 @@ export class Scope {
     /**
      * Builds a tree of namespace objects
      */
-    public buildNamespaceLookup() {
+    public buildNamespaceLookup(options: { okToCache?: boolean } = { okToCache: true }) {
         let namespaceLookup = new Map<string, NamespaceContainer>();
+        options.okToCache = true;
         this.enumerateBrsFiles((file) => {
-
+            options.okToCache = options.okToCache && file.isValidated;
             const fileNamespaceLookup = file.getNamespaceLookupObject();
 
             for (const [lowerNamespaceName, nsContainer] of fileNamespaceLookup) {
@@ -628,9 +645,9 @@ export class Scope {
     }
     private _debugLogComponentName: string;
 
-    public validate(force = false) {
+    public validate(validationOptions: ScopeValidationOptions = { force: false }) {
         //if this scope is already validated, no need to revalidate
-        if (this.isValidated === true && !force) {
+        if (this.isValidated === true && !validationOptions.force) {
             this.logDebug('validate(): already validated');
             return;
         }
@@ -642,10 +659,10 @@ export class Scope {
             //validate our parent before we validate ourself
             if (parentScope && parentScope.isValidated === false) {
                 this.logDebug('validate(): validating parent first');
-                parentScope.validate(force);
+                parentScope.validate(validationOptions);
             }
             //clear the scope's errors list (we will populate them from this method)
-            this.diagnostics = [];
+            this.clearScopeLevelDiagnostics();
 
             let callables = this.getAllCallables();
 
@@ -673,12 +690,13 @@ export class Scope {
 
             //get a list of all callables, indexed by their lower case names
             let callableContainerMap = util.getCallableContainersByLowerName(callables);
-
             //Since statements from files are shared across multiple scopes, we need to link those statements to the current scope
             this.linkSymbolTable();
             const scopeValidateEvent = {
                 program: this.program,
-                scope: this
+                scope: this,
+                changedFiles: validationOptions?.changedFiles,
+                changedSymbols: validationOptions?.changedSymbols
             };
             this.program.plugins.emit('beforeScopeValidate', scopeValidateEvent);
             this.program.plugins.emit('onScopeValidate', scopeValidateEvent);
@@ -709,6 +727,19 @@ export class Scope {
         });
     }
 
+    clearAstSegmentDiagnostics(astSegment: AstNode) {
+        this.diagnostics = this.diagnostics.filter(diag => !(diag.origin === DiagnosticOrigin.ASTSegment && diag.astSegment === astSegment));
+    }
+
+    clearAstSegmentDiagnosticsByFile(file: File) {
+        const lowerSrcPath = file.srcPath.toLowerCase();
+        this.diagnostics = this.diagnostics.filter(diag => !(diag.origin === DiagnosticOrigin.ASTSegment && diag.file.srcPath.toLowerCase() === lowerSrcPath));
+    }
+
+    clearScopeLevelDiagnostics() {
+        this.diagnostics = this.diagnostics.filter(diag => diag.origin !== DiagnosticOrigin.Scope);
+    }
+
     /**
      * Mark this scope as invalid, which means its `validate()` function needs to be called again before use.
      */
@@ -803,6 +834,8 @@ export class Scope {
                 } else {
                     break;
                 }
+            } else {
+                // Existing known namespace
             }
             if (!namespaceTypesKnown.has(nsName)) {
                 namespaceTypesKnown.set(nsName, currentNSType);
@@ -840,6 +873,7 @@ export class Scope {
                 //see if the param matches any starting namespace part
                 if (namespace) {
                     this.diagnostics.push({
+                        origin: DiagnosticOrigin.Scope,
                         file: file,
                         ...DiagnosticMessages.parameterMayNotHaveSameNameAsNamespace(param.name.text),
                         range: param.name.range,
@@ -861,6 +895,7 @@ export class Scope {
             //see if the param matches any starting namespace part
             if (namespace) {
                 this.diagnostics.push({
+                    origin: DiagnosticOrigin.Scope,
                     file: file,
                     ...DiagnosticMessages.variableMayNotHaveSameNameAsNamespace(assignment.name.text),
                     range: assignment.name.range,
@@ -890,7 +925,9 @@ export class Scope {
                     this.diagnostics.push({
                         ...DiagnosticMessages.scopeFunctionShadowedByBuiltInFunction(),
                         range: func.nameRange,
-                        file: file
+                        file: file,
+                        origin: DiagnosticOrigin.Scope
+
                     });
                 }
 
@@ -899,7 +936,8 @@ export class Scope {
                     this.diagnostics.push({
                         ...DiagnosticMessages.functionCannotHaveSameNameAsClass(funcName),
                         range: func.nameRange,
-                        file: file
+                        file: file,
+                        origin: DiagnosticOrigin.Scope
                     });
                 }
             }
@@ -922,7 +960,9 @@ export class Scope {
     private validateClasses() {
         let validator = new BsClassValidator();
         validator.validate(this);
-        this.diagnostics.push(...validator.diagnostics);
+        this.diagnostics.push(...validator.diagnostics.map(diag => {
+            return { ...diag, origin: DiagnosticOrigin.Scope };
+        }));
     }
 
     /**
@@ -950,7 +990,8 @@ export class Scope {
                         this.diagnostics.push({
                             ...DiagnosticMessages.localVarFunctionShadowsParentFunction('stdlib'),
                             range: varDeclaration.nameRange,
-                            file: file
+                            file: file,
+                            origin: DiagnosticOrigin.Scope
                         });
                     }
                 } else if (callableContainerMap.has(lowerVarName)) {
@@ -959,13 +1000,15 @@ export class Scope {
                         this.diagnostics.push({
                             ...DiagnosticMessages.localVarFunctionShadowsParentFunction('scope'),
                             range: varDeclaration.nameRange,
-                            file: file
+                            file: file,
+                            origin: DiagnosticOrigin.Scope
                         });
                     } else {
                         this.diagnostics.push({
                             ...DiagnosticMessages.localVarShadowedByScopedFunction(),
                             range: varDeclaration.nameRange,
-                            file: file
+                            file: file,
+                            origin: DiagnosticOrigin.Scope
                         });
                     }
                     //has the same name as an in-scope class
@@ -973,7 +1016,8 @@ export class Scope {
                     this.diagnostics.push({
                         ...DiagnosticMessages.localVarSameNameAsClass(classMap.get(lowerVarName)?.item.getName(ParseMode.BrighterScript)),
                         range: varDeclaration.nameRange,
-                        file: file
+                        file: file,
+                        origin: DiagnosticOrigin.Scope
                     });
                 }
             }
@@ -1024,7 +1068,8 @@ export class Scope {
                                 shadowedCallable.scope.name
                             ),
                             range: container.callable.nameRange,
-                            file: container.callable.file
+                            file: container.callable.file,
+                            origin: DiagnosticOrigin.Scope
                         });
                     }
                 }
@@ -1044,7 +1089,8 @@ export class Scope {
                             callable.nameRange.start.line,
                             callable.nameRange.end.character
                         ),
-                        file: callable.file
+                        file: callable.file,
+                        origin: DiagnosticOrigin.Scope
                     });
                 }
             }
@@ -1090,14 +1136,16 @@ export class Scope {
                 this.diagnostics.push({
                     ...dInfo,
                     range: scriptImport.filePathRange,
-                    file: scriptImport.sourceFile
+                    file: scriptImport.sourceFile,
+                    origin: DiagnosticOrigin.Scope
                 });
                 //if the character casing of the script import path does not match that of the actual path
             } else if (scriptImport.destPath !== referencedFile.destPath) {
                 this.diagnostics.push({
                     ...DiagnosticMessages.scriptImportCaseMismatch(referencedFile.destPath),
                     range: scriptImport.filePathRange,
-                    file: scriptImport.sourceFile
+                    file: scriptImport.sourceFile,
+                    origin: DiagnosticOrigin.Scope
                 });
             }
         }
diff --git a/src/SymbolTable.ts b/src/SymbolTable.ts
index 104b629d..e052d8e2 100644
--- a/src/SymbolTable.ts
+++ b/src/SymbolTable.ts
@@ -252,9 +252,6 @@ export class SymbolTable implements SymbolTypeGetter {
         let symbols: BscSymbol[] = [].concat(...this.symbolMap.values());
         // eslint-disable-next-line no-bitwise
         symbols = symbols.filter(symbol => symbol.flags & bitFlags);
-        if (this.parent) {
-            symbols = symbols.concat(this.parent.getOwnSymbols(bitFlags));
-        }
         return symbols;
     }
 
@@ -262,7 +259,7 @@ export class SymbolTable implements SymbolTypeGetter {
      * Get list of all symbols declared in this SymbolTable (includes parent SymbolTable).
      */
     public getAllSymbols(bitFlags: SymbolTypeFlag): BscSymbol[] {
-        let symbols = [].concat(...this.symbolMap.values());
+        let symbols: BscSymbol[] = [].concat(...this.symbolMap.values());
         //look through any sibling maps next
         for (let sibling of this.siblings) {
             symbols = symbols.concat(sibling.getAllSymbols(bitFlags));
@@ -271,7 +268,17 @@ export class SymbolTable implements SymbolTypeGetter {
             symbols = symbols.concat(this.parent.getAllSymbols(bitFlags));
         }
         // eslint-disable-next-line no-bitwise
-        return symbols.filter(symbol => symbol.flags & bitFlags);
+        symbols = symbols.filter(symbol => symbol.flags & bitFlags);
+
+        //remove duplicate symbols
+        const symbolsMap = new Map<string, BscSymbol>();
+        for (const symbol of symbols) {
+            const lowerSymbolName = symbol.name.toLowerCase();
+            if (!symbolsMap.has(lowerSymbolName)) {
+                symbolsMap.set(lowerSymbolName, symbol);
+            }
+        }
+        return [...symbolsMap.values()];
     }
 
     private resetTypeCache() {
@@ -348,6 +355,7 @@ export interface BscSymbol {
 }
 
 export interface SymbolTypeGetter {
+    name: string;
     getSymbolType(name: string, options: GetSymbolTypeOptions): BscType;
     setCachedType(name: string, cacheEntry: TypeCacheEntry, options: GetSymbolTypeOptions);
 }
diff --git a/src/UnresolvedNodeSet.ts b/src/UnresolvedNodeSet.ts
deleted file mode 100644
index da7b3b4d..00000000
--- a/src/UnresolvedNodeSet.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { GetSymbolTypeOptions } from './SymbolTable';
-import type { AstNode } from './parser/AstNode';
-import type { BscType } from './types/BscType';
-
-
-//export const RecommendedFileSegmentationWalkMode = WalkMode.walkStatements;
-
-export class UnresolvedNodeSet {
-
-    constructor(public root: AstNode) { }
-
-    private data = new Map<AstNode, Map<number, BscType[]>>();
-
-    reset() {
-        this.data.clear();
-    }
-
-    get nodes() {
-        return this.data.keys();
-    }
-
-
-    private getMap(node: AstNode) {
-        if (!this.data.has(node)) {
-            this.data.set(node, new Map<number, BscType[]>());
-        }
-        return this.data.get(node);
-    }
-    addExpression(node: AstNode, options: GetSymbolTypeOptions) {
-        const listOfResolvedTypes: BscType[] = [];
-        this.getMap(node).set(options.flags, listOfResolvedTypes);
-        return listOfResolvedTypes;
-    }
-
-    addTypeForExpression(node: AstNode, options: GetSymbolTypeOptions, incomingType: BscType) {
-        if (!incomingType.isResolvable()) {
-            return true;
-        }
-        const resolvedTypesList = this.getMap(node).get(options.flags) ?? this.addExpression(node, options);
-
-        let newTypeToCheck = true;
-        for (const resolvedType of resolvedTypesList) {
-            if (resolvedType.isTypeCompatible(incomingType)) {
-                newTypeToCheck = false;
-                break;
-            }
-        }
-        if (newTypeToCheck) {
-            resolvedTypesList.push(incomingType);
-        }
-        return newTypeToCheck;
-    }
-}
diff --git a/src/XmlScope.ts b/src/XmlScope.ts
index 710e1b1f..2a4c8b86 100644
--- a/src/XmlScope.ts
+++ b/src/XmlScope.ts
@@ -2,7 +2,7 @@ import type { Location, Position } from 'vscode-languageserver';
 import { Scope } from './Scope';
 import { DiagnosticMessages } from './DiagnosticMessages';
 import type { XmlFile } from './files/XmlFile';
-import type { CallableContainerMap, FileReference } from './interfaces';
+import { DiagnosticOrigin, type CallableContainerMap, type FileReference } from './interfaces';
 import type { Program } from './Program';
 import util from './util';
 import { isXmlFile } from './astUtils/reflection';
@@ -108,7 +108,8 @@ export class XmlScope extends Scope {
                 this.diagnostics.push({
                     ...DiagnosticMessages.xmlFunctionNotFound(name),
                     range: func.getAttribute('name').tokens.value.range,
-                    file: this.xmlFile
+                    file: this.xmlFile,
+                    origin: DiagnosticOrigin.Scope
                 });
             }
         }
@@ -126,7 +127,8 @@ export class XmlScope extends Scope {
                 this.diagnostics.push({
                     ...DiagnosticMessages.xmlInvalidFieldType(type),
                     range: field.getAttribute('type').tokens.value.range,
-                    file: this.xmlFile
+                    file: this.xmlFile,
+                    origin: DiagnosticOrigin.Scope
                 });
             }
         }
@@ -136,7 +138,8 @@ export class XmlScope extends Scope {
         this.diagnostics.push({
             ...DiagnosticMessages.xmlTagMissingAttribute(tag.tokens.startTagName.text, name),
             range: tag.tokens.startTagName.range,
-            file: this.xmlFile
+            file: this.xmlFile,
+            origin: DiagnosticOrigin.Scope
         });
     }
 
@@ -164,7 +167,8 @@ export class XmlScope extends Scope {
                     this.diagnostics.push({
                         file: this.xmlFile,
                         range: scriptImport.filePathRange,
-                        ...DiagnosticMessages.unnecessaryScriptImportInChildFromParent(ancestorComponentName)
+                        ...DiagnosticMessages.unnecessaryScriptImportInChildFromParent(ancestorComponentName),
+                        origin: DiagnosticOrigin.Scope
                     });
                 }
             }
diff --git a/src/bscPlugin/completions/CompletionsProcessor.spec.ts b/src/bscPlugin/completions/CompletionsProcessor.spec.ts
index 205de007..08d274a1 100644
--- a/src/bscPlugin/completions/CompletionsProcessor.spec.ts
+++ b/src/bscPlugin/completions/CompletionsProcessor.spec.ts
@@ -1130,9 +1130,10 @@ describe('CompletionsProcessor', () => {
                 end sub
             `);
             program.validate();
+            const completions = program.getCompletions('source/main.bs', util.createPosition(3, 24));
             expectCompletionsIncludes(
                 // log(|)
-                program.getCompletions('source/main.bs', util.createPosition(3, 24)),
+                completions,
                 [{
                     label: 'API_KEY',
                     kind: CompletionItemKind.Constant
diff --git a/src/bscPlugin/validation/BrsFileValidator.ts b/src/bscPlugin/validation/BrsFileValidator.ts
index d8bf1938..257e9dcf 100644
--- a/src/bscPlugin/validation/BrsFileValidator.ts
+++ b/src/bscPlugin/validation/BrsFileValidator.ts
@@ -21,6 +21,8 @@ export class BrsFileValidator {
     }
 
     public process() {
+        const unlinkGlobalSymbolTable = this.event.file.parser.symbolTable.pushParentProvider(() => this.event.program.globalScope.symbolTable);
+
         util.validateTooDeepFile(this.event.file);
         this.walk();
         this.flagTopLevelStatements();
@@ -28,13 +30,10 @@ export class BrsFileValidator {
         if (!this.event.file.hasTypedef) {
             this.validateImportStatements();
         }
-
         if (isBrsFile(this.event.file)) {
-            const unlinkGlobalSymbolTable = this.event.file.parser.symbolTable.pushParentProvider(() => this.event.program.globalScope.symbolTable);
-            this.event.file.findUnresolvedSubTrees();
-            unlinkGlobalSymbolTable();
+            this.event.file.processSymbolInformation();
         }
-
+        unlinkGlobalSymbolTable();
     }
 
     /**
@@ -75,7 +74,7 @@ export class BrsFileValidator {
                 const nodeType = node.getType({ flags: SymbolTypeFlag.typetime });
                 node.getSymbolTable().addSymbol('m', undefined, nodeType, SymbolTypeFlag.runtime);
                 // eslint-disable-next-line no-bitwise
-                node.parent.getSymbolTable()?.addSymbol(node.name.text, { definingNode: node }, nodeType, SymbolTypeFlag.typetime | SymbolTypeFlag.runtime);
+                node.parent.getSymbolTable()?.addSymbol(node.name?.text, { definingNode: node }, nodeType, SymbolTypeFlag.typetime | SymbolTypeFlag.runtime);
             },
             AssignmentStatement: (node) => {
                 //register this variable
@@ -147,7 +146,7 @@ export class BrsFileValidator {
 
                 const nodeType = node.getType({ flags: SymbolTypeFlag.typetime });
                 // eslint-disable-next-line no-bitwise
-                node.parent.getSymbolTable().addSymbol(node.tokens.name.text, { definingNode: node }, nodeType, SymbolTypeFlag.runtime | SymbolTypeFlag.typetime);
+                node.parent.getSymbolTable().addSymbol(node.tokens.name.text, { definingNode: node }, nodeType, SymbolTypeFlag.typetime);
             },
             ConstStatement: (node) => {
                 this.validateDeclarationLocations(node, 'const', () => util.createBoundingRange(node.tokens.const, node.tokens.name));
diff --git a/src/bscPlugin/validation/ScopeValidator.spec.ts b/src/bscPlugin/validation/ScopeValidator.spec.ts
index 4fe6cfda..c7d4cfe5 100644
--- a/src/bscPlugin/validation/ScopeValidator.spec.ts
+++ b/src/bscPlugin/validation/ScopeValidator.spec.ts
@@ -1,7 +1,7 @@
 import * as sinonImport from 'sinon';
 import { DiagnosticMessages } from '../../DiagnosticMessages';
 import { Program } from '../../Program';
-import { expectDiagnostics, expectTypeToBe, expectZeroDiagnostics, trim } from '../../testHelpers.spec';
+import { expectDiagnostics, expectDiagnosticsIncludes, expectTypeToBe, expectZeroDiagnostics, trim } from '../../testHelpers.spec';
 import { expect } from 'chai';
 import type { TypeCompatibilityData } from '../../interfaces';
 import { IntegerType } from '../../types/IntegerType';
@@ -1780,22 +1780,85 @@ describe('ScopeValidator', () => {
 
     });
 
-    describe('revalidation reduction', () => {
+    describe('revalidation', () => {
 
-        it('validates only parts of files that need revalidation on scope validation', () => {
+        it('revalidates when a enum defined in a different namespace changes', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                namespace Alpha
+                    function printEnum(enumVal as Alpha.Beta.Charlie.SomeEnum) as string
+                        return enumVal.toStr()
+                    end function
+                end namespace
+            `);
 
+            program.setFile<BrsFile>('source/file2.bs', `
+                namespace Alpha.Beta.Charlie
+                    enum SomeEnum
+                        val1 = 1
+                        val2 = 2
+                    end enum
+                end namespace
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+            program.setFile<BrsFile>('source/file2.bs', `
+                namespace Alpha.Beta.Charlie
+                    enum ChangedEnum
+                        val1 = 1
+                        val2 = 2
+                    end enum
+                end namespace
+            `);
+            program.validate();
+            expectDiagnosticsIncludes(program, [DiagnosticMessages.cannotFindName('SomeEnum').message]);
+        });
+
+        it('revalidates when a class defined in a different namespace changes', () => {
+            program.setFile<BrsFile>('source/file1.bs', `
+                namespace Alpha
+                    function printEnum(myKlass as Alpha.Beta.Charlie.SomeClass) as string
+                        return myKlass.getValue()
+                    end function
+                end namespace
+            `);
+
+            program.setFile<BrsFile>('source/file2.bs', `
+                namespace Alpha.Beta.Charlie
+                    class SomeClass
+                        private myValue as string
+                        function getValue() as string
+                            return m.myValue
+                        end function
+                    end class
+                end namespace
+            `);
+            program.validate();
+            expectZeroDiagnostics(program);
+            program.setFile<BrsFile>('source/file2.bs', `
+                namespace Alpha.Beta.Charlie
+                    class SomeClass
+                        private myValue as string
+                        function getValue(lowerCase as boolean) as string
+                            if lowerCase
+                                return lcase(m.myValue)
+                            end if
+                            return m.myValue
+                        end function
+                    end class
+                end namespace
+            `);
+            program.validate();
+            expectDiagnosticsIncludes(program, [DiagnosticMessages.mismatchArgumentCount(1, 0).message]);
+        });
+
+        it('validates only parts of files that need revalidation on scope validation', () => {
             function validateFile(file: BrsFile) {
                 const validateFileEvent = {
                     program: program,
                     file: file
                 };
-                program.plugins.emit('beforeFileValidate', validateFileEvent);
-
                 //emit an event to allow plugins to contribute to the file validation process
                 program.plugins.emit('onFileValidate', validateFileEvent);
-                file.isValidated = true;
-
-                program.plugins.emit('afterFileValidate', validateFileEvent);
             }
 
             const commonContents = `
@@ -1812,11 +1875,11 @@ describe('ScopeValidator', () => {
             let commonBs: BrsFile = program.setFile('source/common.bs', commonContents);
             validateFile(commonBs);
             expect(commonBs.validationSegmenter.segmentsForValidation.length).to.eq(2); // 1 func,  1 classField
-            expect(commonBs.validationSegmenter.unresolvedSegments.size).to.eq(0);
+            expect(commonBs.validationSegmenter.unresolvedSegmentsSymbols.size).to.eq(0);
             commonBs.validationSegmenter.validatedSegments.forEach(x => expect(x).to.be.false);
             expect(commonBs.validationSegmenter.singleValidationSegments.size).to.eq(2); // no references needed to other files
 
-            let common2Bs: BrsFile = program.setFile('source/common2.bs', `
+            let common2Contents = `
                 sub doesValidationForEachScope()
                     k = new KlassInDiffFile()
                     print k.value
@@ -1825,25 +1888,27 @@ describe('ScopeValidator', () => {
                 function alsoNoValidationForEachScope() as integer
                     return 1
                 end function
-            `);
+            `;
+            let common2Bs: BrsFile = program.setFile('source/common2.bs', common2Contents);
             validateFile(common2Bs);
             expect(common2Bs.validationSegmenter.segmentsForValidation.length).to.eq(2); // 2 func
-            expect(common2Bs.validationSegmenter.unresolvedSegments.size).to.eq(1);
+            expect(common2Bs.validationSegmenter.unresolvedSegmentsSymbols.size).to.eq(1);
             commonBs.validationSegmenter.validatedSegments.forEach(x => expect(x).to.be.false);
             expect(common2Bs.validationSegmenter.singleValidationSegments.size).to.eq(1); // alsoNoValidationForEachScope() does not reference other files
 
-            let klassBs: BrsFile = program.setFile('source/klass.bs', `
+            let klassContents = `
                 class KlassInDiffFile
                     value = 2
                 end class
-            `);
+            `;
+
+            let klassBs: BrsFile = program.setFile('source/klass.bs', klassContents);
             validateFile(klassBs);
             expect(klassBs.validationSegmenter.segmentsForValidation.length).to.eq(1); //  1 classField
-            expect(klassBs.validationSegmenter.unresolvedSegments.size).to.eq(0);
+            expect(klassBs.validationSegmenter.unresolvedSegmentsSymbols.size).to.eq(0);
             klassBs.validationSegmenter.validatedSegments.forEach(x => expect(x).to.be.false);
             expect(klassBs.validationSegmenter.singleValidationSegments.size).to.eq(1); // does not reference other files
 
-
             const widgetFileContents = `
                 sub init()
                     noValidationForEachScope()
@@ -1858,7 +1923,7 @@ describe('ScopeValidator', () => {
 
             validateFile(widgetBs);
             expect(widgetBs.validationSegmenter.segmentsForValidation.length).to.eq(2); // 2 funcs
-            expect(widgetBs.validationSegmenter.unresolvedSegments.size).to.eq(1); // 1 func (init)
+            expect(widgetBs.validationSegmenter.unresolvedSegmentsSymbols.size).to.eq(1); // 1 func (init)
             widgetBs.validationSegmenter.validatedSegments.forEach(x => expect(x).to.be.false);
             expect(widgetBs.validationSegmenter.singleValidationSegments.size).to.eq(1); // 1 func (anotherFunction)
 
@@ -1870,7 +1935,7 @@ describe('ScopeValidator', () => {
             let diffKlassBs: BrsFile = program.setFile('components/diffKlass.bs', diffKlassContent);
             validateFile(diffKlassBs);
             expect(diffKlassBs.validationSegmenter.segmentsForValidation.length).to.eq(1); //  1 classField
-            expect(diffKlassBs.validationSegmenter.unresolvedSegments.size).to.eq(0);
+            expect(diffKlassBs.validationSegmenter.unresolvedSegmentsSymbols.size).to.eq(0);
             diffKlassBs.validationSegmenter.validatedSegments.forEach(x => expect(x).to.be.false);
             expect(diffKlassBs.validationSegmenter.singleValidationSegments.size).to.eq(1);
 
@@ -1884,7 +1949,14 @@ describe('ScopeValidator', () => {
                     <script uri="diffKlass.bs"/>
                 </component>
             `);
-            console.log('****** base validation');
+
+            //reset files
+            commonBs = program.setFile('source/common.bs', commonContents);
+            common2Bs = program.setFile('source/common2.bs', common2Contents);
+            klassBs = program.setFile('source/klass.bs', klassContents);
+            widgetBs = program.setFile('components/Widget.bs', widgetFileContents);
+            diffKlassBs = program.setFile('components/diffKlass.bs', diffKlassContent);
+
             program.validate();
             // all segments should be validated
             [commonBs, common2Bs, klassBs, widgetBs, diffKlassBs].forEach(file => {
diff --git a/src/bscPlugin/validation/ScopeValidator.ts b/src/bscPlugin/validation/ScopeValidator.ts
index 8cbe5f5e..abd33ada 100644
--- a/src/bscPlugin/validation/ScopeValidator.ts
+++ b/src/bscPlugin/validation/ScopeValidator.ts
@@ -3,13 +3,15 @@ import { isAssignmentStatement, isBrsFile, isClassType, isDottedGetExpression, i
 import { Cache } from '../../Cache';
 import { DiagnosticMessages } from '../../DiagnosticMessages';
 import type { BrsFile } from '../../files/BrsFile';
-import type { BsDiagnostic, OnScopeValidateEvent, TypeChainEntry, TypeCompatibilityData } from '../../interfaces';
+import { DiagnosticOrigin } from '../../interfaces';
+import type { BsDiagnostic, BsDiagnosticWithOrigin, OnScopeValidateEvent, TypeChainEntry, TypeCompatibilityData } from '../../interfaces';
 import { SymbolTypeFlag } from '../../SymbolTable';
 import type { AssignmentStatement, DottedSetStatement, EnumStatement, ReturnStatement } from '../../parser/Statement';
 import util from '../../util';
 import { nodes, components } from '../../roku-types';
 import type { BRSComponentData } from '../../roku-types';
 import type { Token } from '../../lexer/Token';
+import type { AstNode } from '../../parser/AstNode';
 import { type Expression } from '../../parser/AstNode';
 import type { VariableExpression, DottedGetExpression, BinaryExpression, UnaryExpression } from '../../parser/Expression';
 import { CallExpression } from '../../parser/Expression';
@@ -54,10 +56,26 @@ export class ScopeValidator {
 
     private walkFiles() {
 
-        //function signatures incompatible cross scopes
-
         this.event.scope.enumerateOwnFiles((file) => {
             if (isBrsFile(file)) {
+                const hasChangeInfo = this.event.changedFiles && this.event.changedSymbols;
+
+                let thisFileRequiresChangedSymbol = false;
+                for (let requiredSymbol of file.requiredSymbols) {
+                    const changeSymbolSetForFlag = this.event.changedSymbols.get(requiredSymbol.flags);
+                    if (util.setContainsUnresolvedSymbol(changeSymbolSetForFlag, requiredSymbol)) {
+                        thisFileRequiresChangedSymbol = true;
+                    }
+                }
+                const thisFileHasChanges = this.event.changedFiles.includes(file);
+                if (hasChangeInfo && !thisFileRequiresChangedSymbol && !thisFileHasChanges) {
+                    // this file does not require a symbol that has changed, and this file has not changed
+                    return;
+                }
+                if (thisFileHasChanges) {
+                    this.event.scope.clearAstSegmentDiagnosticsByFile(file);
+                }
+
                 const validationVisitor = createVisitor({
                     VariableExpression: (varExpr) => {
                         this.validateVariableAndDottedGetExpressions(file, varExpr);
@@ -82,9 +100,13 @@ export class ScopeValidator {
                         this.validateUnaryExpression(file, unaryExpr);
                     }
                 });
+                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
 
-                const segmentsToWalkForValidation = file.getValidationSegments();
                 for (const segment of segmentsToWalkForValidation) {
+                    this.currentSegmentBeingValidated = segment;
+                    this.event.scope.clearAstSegmentDiagnostics(segment);
                     segment.walk(validationVisitor, {
                         walkMode: InsideSegmentWalkMode
                     });
@@ -94,6 +116,8 @@ export class ScopeValidator {
         });
     }
 
+    private currentSegmentBeingValidated: AstNode;
+
 
     private isTypeKnown(exprType: BscType) {
         let isKnownType = exprType?.isResolvable();
@@ -115,7 +139,7 @@ export class ScopeValidator {
      * Flag duplicate enums
      */
     private detectDuplicateEnums() {
-        const diagnostics: BsDiagnostic[] = [];
+        const diagnostics: BsDiagnosticWithOrigin[] = [];
         const enumLocationsByName = new Cache<string, Array<{ file: BrsFile; statement: EnumStatement }>>();
         this.event.scope.enumerateBrsFiles((file) => {
             for (const enumStatement of file.parser.references.enumStatements) {
@@ -156,7 +180,8 @@ export class ScopeValidator {
                             URI.file(primaryEnum.file.srcPath).toString(),
                             primaryEnum.statement.tokens.name.range
                         )
-                    }]
+                    }],
+                    origin: DiagnosticOrigin.Scope
                 });
             }
         }
@@ -530,18 +555,20 @@ export class ScopeValidator {
         } else if (isDynamicType(exprType) && isEnumType(parentTypeInfo?.type) && isDottedGetExpression(expression)) {
             const enumFileLink = this.event.scope.getEnumFileLink(util.getAllDottedGetPartsAsString(expression.obj));
             const typeChainScanForParent = util.processTypeChain(typeChain.slice(0, -1));
-            this.addMultiScopeDiagnostic({
-                file: file,
-                ...DiagnosticMessages.unknownEnumValue(lastTypeInfo?.name, typeChainScanForParent.fullChainName),
-                range: lastTypeInfo?.range,
-                relatedInformation: [{
-                    message: 'Enum declared here',
-                    location: util.createLocation(
-                        URI.file(enumFileLink?.file.srcPath).toString(),
-                        enumFileLink?.item?.tokens.name.range
-                    )
-                }]
-            });
+            if (enumFileLink) {
+                this.addMultiScopeDiagnostic({
+                    file: file,
+                    ...DiagnosticMessages.unknownEnumValue(lastTypeInfo?.name, typeChainScanForParent.fullChainName),
+                    range: lastTypeInfo?.range,
+                    relatedInformation: [{
+                        message: 'Enum declared here',
+                        location: util.createLocation(
+                            URI.file(enumFileLink?.file.srcPath).toString(),
+                            enumFileLink?.item?.tokens.name.range
+                        )
+                    }]
+                });
+            }
         }
     }
 
@@ -551,14 +578,30 @@ export class ScopeValidator {
      */
     private addDiagnosticOnce(diagnostic: BsDiagnostic) {
         this.onceCache.getOrAdd(`${diagnostic.code}-${diagnostic.message}-${util.rangeToString(diagnostic.range)}`, () => {
-            this.event.scope.addDiagnostics([diagnostic]);
+            const diagnosticWithOrigin = { ...diagnostic } as BsDiagnosticWithOrigin;
+            if (!diagnosticWithOrigin.origin) {
+                // diagnostic does not have origin.
+                // set the origin to the current astSegment
+                diagnosticWithOrigin.origin = DiagnosticOrigin.ASTSegment;
+                diagnosticWithOrigin.astSegment = this.currentSegmentBeingValidated;
+            }
+
+            this.event.scope.addDiagnostics([diagnosticWithOrigin]);
             return true;
         });
     }
     private onceCache = new Cache<string, boolean>();
 
     private addDiagnostic(diagnostic: BsDiagnostic) {
-        this.event.scope.addDiagnostics([diagnostic]);
+        const diagnosticWithOrigin = { ...diagnostic } as BsDiagnosticWithOrigin;
+        if (!diagnosticWithOrigin.origin) {
+            // diagnostic does not have origin.
+            // set the origin to the current astSegment
+            diagnosticWithOrigin.origin = DiagnosticOrigin.ASTSegment;
+            diagnosticWithOrigin.astSegment = this.currentSegmentBeingValidated;
+        }
+
+        this.event.scope.addDiagnostics([diagnosticWithOrigin]);
     }
 
     /**
@@ -566,11 +609,21 @@ export class ScopeValidator {
      */
     private addMultiScopeDiagnostic(diagnostic: BsDiagnostic) {
         diagnostic = this.multiScopeCache.getOrAdd(`${diagnostic.file?.srcPath}-${diagnostic.code}-${diagnostic.message}-${util.rangeToString(diagnostic.range)}`, () => {
+
             if (!diagnostic.relatedInformation) {
                 diagnostic.relatedInformation = [];
             }
-            this.addDiagnostic(diagnostic);
-            return diagnostic;
+
+            const diagnosticWithOrigin = { ...diagnostic } as BsDiagnosticWithOrigin;
+            if (!diagnosticWithOrigin.origin) {
+                // diagnostic does not have origin.
+                // set the origin to the current astSegment
+                diagnosticWithOrigin.origin = DiagnosticOrigin.ASTSegment;
+                diagnosticWithOrigin.astSegment = this.currentSegmentBeingValidated;
+            }
+
+            this.addDiagnostic(diagnosticWithOrigin);
+            return diagnosticWithOrigin;
         });
         if (isXmlScope(this.event.scope) && this.event.scope.xmlFile?.srcPath) {
             diagnostic.relatedInformation.push({
@@ -591,5 +644,5 @@ export class ScopeValidator {
         }
     }
 
-    private multiScopeCache = new Cache<string, BsDiagnostic>();
+    private multiScopeCache = new Cache<string, BsDiagnosticWithOrigin>();
 }
diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts
index f79053ea..315ff495 100644
--- a/src/files/BrsFile.spec.ts
+++ b/src/files/BrsFile.spec.ts
@@ -22,6 +22,8 @@ import * as fsExtra from 'fs-extra';
 import { URI } from 'vscode-uri';
 import undent from 'undent';
 import { tempDir, rootDir } from '../testHelpers.spec';
+import { SymbolTypeFlag } from '../SymbolTable';
+import { ClassType, EnumType, FloatType, InterfaceType } from '../types';
 
 let sinon = sinonImport.createSandbox();
 
@@ -3086,4 +3088,848 @@ describe('BrsFile', () => {
             range: util.createRange(4, 12, 4, 24)
         }]);
     });
+
+    describe('requiredSymbols', () => {
+        it('should be empty for a simple file', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                function someFunc() as integer
+                    return 1
+                end function
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(0);
+        });
+
+        it('should be empty if the file needs no external symbols', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                function someFunc() as integer
+                    return 1
+                end function
+
+                sub useKlass()
+                    k = new Klass()
+                    k.addTwo()
+                end sub
+
+                class Klass
+                    sub addTwo()
+                        print someFunc() + someFunc()
+                    end sub
+                end class
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(0);
+        });
+
+        it('should not include global callables or types', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                function printLower(s as string) as integer
+                    print lcase(s.trim())
+                end function
+
+                sub setLabelText( label as roSGNodeLabel, text as string)
+                    label.text = text
+                end sub
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(0);
+        });
+
+        it('should include unknown param and return types', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                function someFunc(arg as OneType) as TwoType
+                    return arg.getTwo()
+                end function
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(2);
+            expect(mainFile.requiredSymbols.map(x => x.typeChain[0].name)).to.have.same.members([
+                'TwoType', 'OneType']);
+        });
+
+        it('should include unknown param and return types on class methods', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                class Klass
+                    function someFunc(arg as OneType) as TwoType
+                        return arg.getTwo()
+                    end function
+                end class
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(2);
+            expect(mainFile.requiredSymbols.map(x => x.typeChain[0].name)).to.have.same.members([
+                'TwoType', 'OneType']);
+        });
+
+        it('should not include assigned symbols', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                sub someFunc(arg as SomeOtherType)
+                    x = arg.member
+                    print x+1
+                end sub
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(1);
+            // x and arg are assigned.. they are not included in the required symbols
+            expect(mainFile.requiredSymbols[0].typeChain[0].name).to.equal('SomeOtherType');
+        });
+
+        it('should include functions called that are not in the file', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                sub someFunc()
+                    x = otherFileFunc1()
+                    print x+1
+                end sub
+
+                function deepFunctionCall(i as integer)
+                    x = 2*i and otherFileFunc2()
+                    y = sin(x+fix(78.2)*log(otherFileFunc3()))
+                    ' this is a comment otherFileFunc5()
+                    return y-otherFileFunc4()
+                end function
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(4);
+            expect(mainFile.requiredSymbols.map(x => x.typeChain[0].name)).to.have.same.members([
+                'otherFileFunc1', 'otherFileFunc2', 'otherFileFunc3', 'otherFileFunc4'
+            ]);
+        });
+
+        it('should include classes called that are not in the file', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                function someFunc(other as OtherKlass) as NS1.Thing
+                    x = new AnotherClass()
+                    return other.getThing(x)
+                end function
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(3);
+            const requiredTypeChains = mainFile.requiredSymbols.map(x => x.typeChain.map(tc => tc.name).join('.'));
+            expect(requiredTypeChains).to.have.same.members([
+                'OtherKlass', 'NS1.Thing', 'AnotherClass'
+            ]);
+            const requiredSymbolsFlags = mainFile.requiredSymbols.map(x => x.flags);
+            expect(requiredSymbolsFlags).to.have.same.members([
+                SymbolTypeFlag.typetime, SymbolTypeFlag.typetime, SymbolTypeFlag.runtime
+            ]);
+        });
+
+        it('should include enums and consts that are not in the file', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                sub someFunc(myEnum as SomeEnum)
+                    if myEnum = SomeEnum.value1
+                        print 1
+                    else if myEnum = SomeEnum.value2
+                        print 2
+                    else if myEnum = SomeConstValue
+                        print 3
+                    end if
+                end sub
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(4);
+            const requiredTypeChains = mainFile.requiredSymbols.map(x => x.typeChain.map(tc => tc.name).join('.'));
+            expect(requiredTypeChains).to.have.same.members([
+                'SomeEnum', 'SomeEnum.value1', 'SomeEnum.value2', 'SomeConstValue'
+            ]);
+            const requiredSymbolsFlags = mainFile.requiredSymbols.map(x => x.flags);
+            expect(requiredSymbolsFlags).to.have.same.members([
+                SymbolTypeFlag.typetime, SymbolTypeFlag.runtime, SymbolTypeFlag.runtime, SymbolTypeFlag.runtime
+            ]);
+        });
+
+        it('should include types not defined in the file', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                interface Data
+                    kind as DataKind
+                    getObj as DataObject
+                    subData as SubData
+                end interface
+
+                class DataObject extends BaseData
+                    kind as DataKind
+                    function process(dataProcess as DataProcessor) as ProcessedData
+                        return dataProcess.work(m)
+                    end function
+                end class
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(5);
+            const requiredTypeChains = mainFile.requiredSymbols.map(x => x.typeChain.map(tc => tc.name).join('.'));
+            expect(requiredTypeChains).to.have.same.members([
+                'DataKind', 'SubData', 'BaseData', 'DataProcessor', 'ProcessedData'
+            ]);
+            const requiredSymbolsFlags = mainFile.requiredSymbols.map(x => x.flags);
+            expect(requiredSymbolsFlags).to.have.same.members([
+                SymbolTypeFlag.typetime, SymbolTypeFlag.typetime, SymbolTypeFlag.typetime, SymbolTypeFlag.typetime, SymbolTypeFlag.typetime
+            ]);
+        });
+
+        it('includes namespace details', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                namespace Alpha.Beta
+                    sub printConstVal()
+                        print CONST_VALUE
+                    end sub
+                end namespace
+
+                namespace Delta
+                    namespace Gamma
+                        namespace Eta
+                            sub doStuff(x as OtherType)
+                                x.something()
+                            end sub
+                        end namespace
+                    end namespace
+                end namespace
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+
+            expect(mainFile.requiredSymbols.length).to.eq(2);
+            const requiredTypeChains = mainFile.requiredSymbols.map(x => x.typeChain.map(tc => tc.name).join('.'));
+            expect(requiredTypeChains).to.have.same.members([
+                'CONST_VALUE', 'OtherType'
+            ]);
+            expect(mainFile.requiredSymbols[0].containingNamespaces).to.have.same.members(['Alpha', 'Beta']);
+            expect(mainFile.requiredSymbols[1].containingNamespaces).to.have.same.members(['Delta', 'Gamma', 'Eta']);
+        });
+        it('does not include namespaces that are defined in the file', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                namespace name1
+                    const PI = 3.14
+
+                    namespace name2
+                        function getPi() as float
+                            return name1.PI
+                        end function
+                    end namespace
+                end namespace
+            `);
+            program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+            expect(mainFile.requiredSymbols.length).to.eq(0);
+        });
+
+
+        it('should put types from typecasts as typetime required', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                function takesIface(z) as string
+                    return (z as MyInterface).name
+                end function
+            `);
+            program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+            expect(mainFile.requiredSymbols.length).to.eq(1);
+            expect(mainFile.requiredSymbols[0].flags).to.eq(SymbolTypeFlag.typetime);
+        });
+    });
+
+    describe('providedSymbols', () => {
+
+        it('includes functions defined in the file', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                function someFunc() as integer
+                    return 1
+                end function
+
+                function someFunc2() as float
+                    return 2.3
+                end function
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+            const runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+            expect(runtimeSymbols.size).to.eq(2);
+            const someFuncType = runtimeSymbols.get('somefunc');
+            expectTypeToBe(someFuncType, TypedFunctionType);
+            const someFunc2Type = runtimeSymbols.get('somefunc2');
+            expectTypeToBe(someFunc2Type, TypedFunctionType);
+        });
+
+        it('includes functions with unresolved params/return types', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                function someFunc() as OtherFileType
+                    return new OtherFileType()
+                end function
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+            const runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+            expect(runtimeSymbols.size).to.eq(1);
+            const someFuncType = runtimeSymbols.get('somefunc');
+            expectTypeToBe(someFuncType, TypedFunctionType);
+            const requiredSymbols = mainFile.requiredSymbols.map(x => x.typeChain[0].name);
+            expect(requiredSymbols).to.have.same.members(['OtherFileType', 'OtherFileType']);
+            const requiredSymbolTypes = mainFile.requiredSymbols.map(x => x.flags);
+            expect(requiredSymbolTypes).to.have.same.members([SymbolTypeFlag.runtime, SymbolTypeFlag.typetime]);
+        });
+
+        it('includes classes defined in the file', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                class Klass
+                    name as string
+                end class
+
+                class Klass2 extends Klass
+                    age as integer
+
+                    function getId() as string
+                        return m.name + " " + m.age.toStr()
+                    end function
+                end class
+
+                class Klass3
+                    propClass = new Klass2()
+                end class
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+            const runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+            expect(runtimeSymbols.size).to.eq(3);
+            expectTypeToBe(runtimeSymbols.get('klass'), ClassType);
+            expectTypeToBe(runtimeSymbols.get('klass2'), ClassType);
+            expectTypeToBe(runtimeSymbols.get('klass3'), ClassType);
+            const typetimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.typetime);
+            expect(typetimeSymbols.size).to.eq(3);
+            expectTypeToBe(runtimeSymbols.get('klass'), ClassType);
+            expectTypeToBe(runtimeSymbols.get('klass2'), ClassType);
+            expectTypeToBe(runtimeSymbols.get('klass3'), ClassType);
+        });
+
+        it('includes other types defined in the file', () => {
+            const mainFile: BrsFile = program.setFile('source/main.bs', `
+                interface MyInterface
+                    name as string
+                end interface
+
+                enum MyEnum
+                    val1
+                    val2
+                end enum
+
+                namespace MyNamespace
+                    const MyConst = 3.14
+                end namespace
+            `);
+            const validateFileEvent = {
+                program: program,
+                file: mainFile
+            };
+            program.plugins.emit('onFileValidate', validateFileEvent);
+            const runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+            expect(runtimeSymbols.size).to.eq(2);
+            expectTypeToBe(runtimeSymbols.get('myenum'), EnumType);
+            expectTypeToBe(runtimeSymbols.get('mynamespace.myconst'), FloatType);
+            const typetimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.typetime);
+            expect(typetimeSymbols.size).to.eq(2);
+            expectTypeToBe(typetimeSymbols.get('myinterface'), InterfaceType);
+            expectTypeToBe(runtimeSymbols.get('myenum'), EnumType);
+        });
+
+        describe('changes', () => {
+
+            it('new symbols are added to the changes set', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    sub someFunc()
+                        print 1
+                    end sub
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+
+                mainFile = program.setFile('source/main.bs', `
+                    sub someFunc()
+                        print 1
+                    end sub
+
+                    sub someFunc2()
+                        print 2
+                    end sub
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(2);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(1);
+                expect(runtimeChanges.has('somefunc2')).to.be.true;
+            });
+
+            it('removed symbols are added to the changes set', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    sub someFunc()
+                        print 1
+                    end sub
+
+                    sub someFunc2()
+                        print 2
+                    end sub
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(2);
+
+                mainFile = program.setFile('source/main.bs', `
+                    sub someFunc()
+                        print 1
+                    end sub
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(1);
+                expect(runtimeChanges.has('somefunc2')).to.be.true;
+            });
+
+            it('new symbols in a namespace are added to the changes set', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    namespace Alpha
+                    end namespace
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(0);
+
+                mainFile = program.setFile('source/main.bs', `
+                    namespace Alpha
+                        const ABC = "abc"
+                    end namespace
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(1);
+                expect(runtimeChanges.has('alpha.abc')).to.be.true;
+            });
+
+            it('should be empty if no changes in actual provided symbols', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    sub printSomething()
+                        print "Something"
+                    end sub
+
+                    namespace alpha.beta
+                        const PI = 3.14
+                    end namespace
+                `);
+
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(2);
+
+                mainFile = program.setFile('source/main.bs', `
+                    sub printSomething()
+                        print "Something Else"
+                    end sub
+
+                    namespace alpha.beta
+                        const PI = 3.14159
+                    end namespace
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(2);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(0);
+            });
+
+            it('should include changes in function signatures', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    function someFunc(x)
+                        return x
+                    end function
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+
+                mainFile = program.setFile('source/main.bs', `
+                    function someFunc(x, y)
+                        return x+y
+                    end function
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(1);
+                expect(runtimeChanges.has('somefunc'));
+            });
+
+            it('should include changes in classes', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    class MyKlass
+                        name as string
+                        function getValue() as float
+                            return 3.14
+                        end function
+                    end class
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+
+                mainFile = program.setFile('source/main.bs', `
+                    class MyKlass
+                        name as string
+                        function getValue() as string
+                            return "hello"
+                        end function
+                    end class
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(1);
+                expect(runtimeChanges.has('myklass'));
+                let typeTimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.typetime);
+                expect(typeTimeChanges.size).to.eq(1);
+                expect(typeTimeChanges.has('myklass'));
+            });
+
+
+            it('should include changes in interfaces', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    interface Iface1
+                        name as string
+                        function doStuff() as float
+                    end interface
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let typetimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.typetime);
+                expect(typetimeSymbols.size).to.eq(1);
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(0);
+
+                mainFile = program.setFile('source/main.bs', `
+                    interface Iface1
+                        name as string
+                        age as integer
+                        function doStuff() as float
+                    end interface
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                typetimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.typetime);
+                expect(typetimeSymbols.size).to.eq(1);
+                let typeTimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.typetime);
+                expect(typeTimeChanges.size).to.eq(1);
+                expect(typeTimeChanges.has('iface1'));
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(0);
+            });
+
+            it('should not include changes in enum values, if inner type is the same', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    enum MyEnum
+                        north = 4
+                        east = 3
+                        south = 2
+                        west = 1
+                    end enum
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+
+                mainFile = program.setFile('source/main.bs', `
+                    enum MyEnum
+                        north = 1
+                        east = 2
+                        south = 3
+                        west = 4
+                    end enum
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(0);
+
+                let typetimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.typetime);
+                expect(typetimeSymbols.size).to.eq(1);
+                let typetimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.typetime);
+                expect(typetimeChanges.size).to.eq(0);
+            });
+
+            it('should include changes in enum, if different number of members', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    enum Direction
+                        north = 1
+                        east = 2
+                        south = 3
+                        west = 4
+                    end enum
+
+                    enum Weather
+                        rainy
+                        sunny
+                    end enum
+
+                    enum Colors
+                        blue
+                        red
+                        green
+                        purple
+                    end enum
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(3);
+
+                mainFile = program.setFile('source/main.bs', `
+                    enum Direction ' same
+                        north = 1
+                        east = 2
+                        south = 3
+                        west = 4
+                    end enum
+
+                    enum Weather 'added member
+                        rainy
+                        sunny
+                        snowy
+                    end enum
+
+                    enum Colors 'removed member
+                        blue
+                        red
+                        green
+                    end enum
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(3);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(2);
+                expect(runtimeChanges.has('weather'));
+                expect(runtimeChanges.has('colors'));
+
+            });
+
+            it('should include changes in enum, if different underlying type', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    enum Direction
+                        north = 1
+                        east = 2
+                        south = 3
+                        west = 4
+                    end enum
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+
+                mainFile = program.setFile('source/main.bs', `
+                    enum Direction ' now is a string
+                        north = "N"
+                        east = "E"
+                        south = "S"
+                        west = "W"
+                    end enum
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(1);
+                expect(runtimeChanges.has('direction'));
+            });
+
+            it('should include changes in const, if different underlying type', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    namespace alpha.beta
+                        const PI = 3.14
+                    end namespace
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+
+                mainFile = program.setFile('source/main.bs', `
+                    namespace alpha.beta
+                        const PI = "lemon chiffon"
+                    end namespace
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(1);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(1);
+                expect(runtimeChanges.has('alpha.beta.pi'));
+            });
+
+            it('should not include changes inside a function if the param types are known', () => {
+                let mainFile: BrsFile = program.setFile('source/main.bs', `
+                    function func1(p as string) as integer
+                        return len(p)
+                    end function
+
+                    sub displayModelTypeInLabel(myLabel as roSgNodeLabel)
+                        print myLabel.text
+                        di = createObject("roDeviceInfo")' as roDeviceInfo
+                        myLabel.text = di.GetFriendlyName()
+                        print myLabel.getChildren(0, -1)
+                    end sub
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(2);
+
+                mainFile = program.setFile('source/main.bs', `
+                    function func1(p as string) as integer
+                        return len(p)+1
+                    end function
+
+                    sub displayModelTypeInLabel(myLabel as roSgNodeLabel)
+                        print myLabel.text
+                        di = createObject("roDeviceInfo") as roDeviceInfo
+                        myLabel.text = di.GetFriendlyName()
+                        print myLabel.getChildren(0, -1)
+                    end sub
+                `);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime);
+                expect(runtimeSymbols.size).to.eq(2);
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(0);
+            });
+
+            it('classes that override AA built-in methods show change properly', () => {
+                const classFileContent = `
+                    class AAOverRide
+                        sub count(num as integer) as void
+                            print num
+                        end sub
+                    end class
+                `;
+
+                let mainFile: BrsFile = program.setFile<BrsFile>('source/class.bs', classFileContent);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                // No changes!
+                mainFile = program.setFile<BrsFile>('source/class.bs', classFileContent);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(0);
+            });
+
+
+            it('functions in a namespace that return classes show change properly', () => {
+                const fileContent = `
+                    namespace Alpha.Beta
+
+                        class SomeKlass
+                            name as string
+                            function combineName(klass as SomeKlass)
+                                m.name = m.name+klass.name
+                            end function
+                        end class
+
+                        function getSomeKlass(name as string) as SomeKlass
+                            k = new SomeKlass()
+                            k.name = name
+                            return k
+                        end function
+                    end namespace
+                `;
+
+                let mainFile: BrsFile = program.setFile<BrsFile>('source/class.bs', fileContent);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                // No changes!
+                mainFile = program.setFile<BrsFile>('source/class.bs', fileContent);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(0);
+            });
+
+            it('functions in a namespace that have class params show change properly', () => {
+                const fileContent = `
+                    namespace Alpha.Beta
+
+                        class SomeKlass
+                            name as string
+                            function combineName(klass as SomeKlass)
+                                m.name = m.name+klass.name
+                            end function
+                        end class
+
+                        function combineKlass(klass1 as SomeKlass, klass2 as SomeKlass) as SomeKlass
+                            klass1.combineName(klass2)
+                            return klass1
+                        end function
+                    end namespace
+                `;
+                let mainFile: BrsFile = program.setFile<BrsFile>('source/class.bs', fileContent);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                // No changes!
+                mainFile = program.setFile<BrsFile>('source/class.bs', fileContent);
+                program.plugins.emit('onFileValidate', { program: program, file: mainFile });
+                let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime);
+                expect(runtimeChanges.size).to.eq(0);
+            });
+        });
+    });
 });
diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts
index 8fc60c3e..cef392bf 100644
--- a/src/files/BrsFile.ts
+++ b/src/files/BrsFile.ts
@@ -21,7 +21,7 @@ import { BrsTranspileState } from '../parser/BrsTranspileState';
 import { Preprocessor } from '../preprocessor/Preprocessor';
 import { LogLevel } from '../Logger';
 import { serializeError } from 'serialize-error';
-import { isMethodStatement, isClassStatement, isDottedGetExpression, isFunctionStatement, isLiteralExpression, isNamespaceStatement, isStringType, isVariableExpression, isImportStatement, isFieldStatement, isFunctionExpression, isBrsFile, isEnumStatement, isConstStatement } from '../astUtils/reflection';
+import { isMethodStatement, isClassStatement, isDottedGetExpression, isFunctionStatement, isLiteralExpression, isNamespaceStatement, isStringType, isVariableExpression, isImportStatement, isFieldStatement, isFunctionExpression, isBrsFile, isEnumStatement, isConstStatement, isAnyReferenceType } from '../astUtils/reflection';
 import { createVisitor, WalkMode } from '../astUtils/visitors';
 import type { DependencyGraph } from '../DependencyGraph';
 import { CommentFlagProcessor } from '../CommentFlagProcessor';
@@ -31,7 +31,19 @@ import { type Expression } from '../parser/AstNode';
 import { SymbolTable, SymbolTypeFlag } from '../SymbolTable';
 import type { File } from './File';
 import { Editor } from '../astUtils/Editor';
+import type { UnresolvedSymbol } from '../AstValidationSegmenter';
 import { AstValidationSegmenter } from '../AstValidationSegmenter';
+import type { BscType } from '../types';
+
+
+export type ProvidedSymbolMap = Map<SymbolTypeFlag, Map<string, BscType>>;
+export type ChangedSymbolMap = Map<SymbolTypeFlag, Set<string>>;
+
+export interface ProvidedSymbolInfo {
+    symbolMap: ProvidedSymbolMap;
+    changes: ChangedSymbolMap;
+}
+
 
 /**
  * Holds all details about this file within the scope of the whole program
@@ -1324,27 +1336,130 @@ export class BrsFile implements File {
         }
     }
 
-    static reduceReValidation = true;
-
     public validationSegmenter = new AstValidationSegmenter();
 
-    public findUnresolvedSubTrees() {
-        if (BrsFile.reduceReValidation) {
-            this.validationSegmenter.processTree(this.ast);
-        }
+    public processSymbolInformation() {
+        this.validationSegmenter.processTree(this.ast);
+        this.program.addFileSymbolInfo(this);
+    }
+
+    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();
+
+            const requiredSymbols: UnresolvedSymbol[] = [];
+            const addedSymbols = new Map<SymbolTypeFlag, Set<string>>();
+            addedSymbols.set(SymbolTypeFlag.runtime, new Set());
+            addedSymbols.set(SymbolTypeFlag.typetime, new Set());
+            for (const setOfSymbols of allNeededSymbolSets) {
+                for (const symbol of setOfSymbols) {
+
+                    const fullSymbolkey = symbol.typeChain.map(tce => tce.name).join('.').toLowerCase();
+                    if (this.providedSymbols.symbolMap.get(symbol.flags)?.has(fullSymbolkey)) {
+                        // this catches namespaced things
+                        continue;
+                    }
+                    if (!addedSymbols.get(symbol.flags).has(fullSymbolkey)) {
+                        requiredSymbols.push(symbol);
+                        addedSymbols.get(symbol.flags).add(fullSymbolkey);
+                    }
+                }
+            }
+            return requiredSymbols;
+        });
+    }
+
+    public get providedSymbols() {
+        return this.cache?.getOrAdd(`providedSymbols`, () => {
+            return this.getProvidedSymbols();
+        });
     }
 
-    public getValidationSegments() {
-        // return this.cache.getOrAdd(`validationSegments`, () => {
-        if (BrsFile.reduceReValidation) {
-            //const t0 = performance.now();
-            const segments = this.validationSegmenter.getSegments();
-            //const t1 = performance.now();
-            // console.log('validationSegmenter.getSegments()', this.pkgPath, t1 - t0);
-            return segments;
+    private getProvidedSymbols() {
+        const symbolMap = new Map<SymbolTypeFlag, Map<string, BscType>>();
+        const runTimeSymbolMap = new Map<string, BscType>();
+        const typeTimeSymbolMap = new Map<string, BscType>();
+
+        const tablesToGetSymbolsFrom: Array<{ table: SymbolTable; namePrefixLower?: string }> = [{
+            table: this.parser.symbolTable
+        }];
+
+        for (const namespaceStatement of this.parser.references.namespaceStatements) {
+            tablesToGetSymbolsFrom.push({
+                table: namespaceStatement.body.getSymbolTable(),
+                namePrefixLower: namespaceStatement.getName(ParseMode.BrighterScript).toLowerCase()
+            });
+        }
+
+        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)) {
+                    const symbolNameLower = symbolTable.namePrefixLower ? `${symbolTable.namePrefixLower}.${symbol.name.toLowerCase()}` : symbol.name.toLowerCase();
+                    runTimeSymbolMap.set(symbolNameLower, symbol.type);
+                }
+            }
+
+            for (const symbol of typeTimeSymbols) {
+                if (!isAnyReferenceType(symbol.type)) {
+                    const symbolNameLower = symbolTable.namePrefixLower ? `${symbolTable.namePrefixLower}.${symbol.name.toLowerCase()}` : symbol.name.toLowerCase();
+                    typeTimeSymbolMap.set(symbolNameLower, symbol.type);
+                }
+            }
+        }
+
+        symbolMap.set(SymbolTypeFlag.runtime, runTimeSymbolMap);
+        symbolMap.set(SymbolTypeFlag.typetime, typeTimeSymbolMap);
+
+        const changes = new Map<SymbolTypeFlag, Set<string>>();
+        changes.set(SymbolTypeFlag.runtime, new Set<string>());
+        changes.set(SymbolTypeFlag.typetime, new Set<string>());
+        const previouslyProvidedSymbols = this.program.getFileSymbolInfo(this)?.provides.symbolMap;
+        const previousSymbolsChecked = new Map<SymbolTypeFlag, Set<string>>();
+        previousSymbolsChecked.set(SymbolTypeFlag.runtime, new Set<string>());
+        previousSymbolsChecked.set(SymbolTypeFlag.typetime, new Set<string>());
+        for (const flag of [SymbolTypeFlag.runtime, SymbolTypeFlag.typetime]) {
+            const newSymbolMapForFlag = symbolMap.get(flag);
+            const oldSymbolMapForFlag = previouslyProvidedSymbols?.get(flag);
+            const previousSymbolsCheckedForFlag = previousSymbolsChecked.get(flag);
+            const changesForFlag = changes.get(flag);
+            if (!oldSymbolMapForFlag) {
+                for (const key of newSymbolMapForFlag.keys()) {
+                    changesForFlag.add(key);
+                }
+                continue;
+
+            }
+            for (const [symbolKey, symbolType] of newSymbolMapForFlag) {
+                const previousType = oldSymbolMapForFlag?.get(symbolKey);
+                previousSymbolsCheckedForFlag.add(symbolKey);
+                if (!previousType) {
+                    changesForFlag.add(symbolKey);
+                    continue;
+                }
+                const data = {};
+                if (!symbolType.isEqual(previousType, data)) {
+                    changesForFlag.add(symbolKey);
+                }
+            }
+            for (const [symbolKey] of previouslyProvidedSymbols.get(flag)) {
+                if (!previousSymbolsCheckedForFlag.has(symbolKey)) {
+                    changesForFlag.add(symbolKey);
+                }
+            }
         }
-        return [this.ast];
-        // });
+        return {
+            symbolMap: symbolMap,
+            changes: changes
+        };
     }
 
     public markSegmentAsValidated(node: AstNode) {
@@ -1352,11 +1467,11 @@ export class BrsFile implements File {
     }
 
     public getNamespaceLookupObject() {
+        if (!this.isValidated) {
+            return this.buildNamespaceLookup();
+        }
         return this.cache.getOrAdd(`namespaceLookup`, () => {
-            //const t0 = performance.now();
             const nsLookup = this.buildNamespaceLookup();
-            // const t1 = performance.now();
-            //console.log('buildNamespaceLookup()', this.pkgPath, t1 - t0);
             return nsLookup;
         });
     }
@@ -1406,13 +1521,11 @@ export class BrsFile implements File {
                         // the aggregate symbol table should have no parent. It should include just the symbols of the namespace.
                         symbolTable: new SymbolTable(`Namespace Aggregate: '${loopName}'`)
                     });
-                } else {
-
                 }
             }
             let ns = namespaceLookup.get(lowerLoopName);
             ns.namespaceStatements.push(namespaceStatement);
-            //ns.statements.push(...namespaceStatement.body.statements);
+            ns.statements.push(...namespaceStatement.body.statements);
             for (let statement of namespaceStatement.body.statements) {
                 if (isClassStatement(statement) && statement.name) {
                     ns.classStatements.set(statement.name.text.toLowerCase(), statement);
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 7d4c6391..aab1c697 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -30,6 +30,18 @@ export interface BsDiagnostic extends Diagnostic {
     data?: any;
 }
 
+export enum DiagnosticOrigin {
+    Program = 'Program',
+    Scope = 'Scope',
+    File = 'File',
+    ASTSegment = 'AstSegment'
+}
+
+export interface BsDiagnosticWithOrigin extends BsDiagnostic {
+    origin: DiagnosticOrigin;
+    astSegment?: AstNode;
+}
+
 export interface Callable {
     file: File;
     name: string;
@@ -536,9 +548,17 @@ export interface TranspileEntry {
     outputPath: string;
 }
 
+export interface ScopeValidationOptions {
+    changedFiles?: File[];
+    changedSymbols?: Map<SymbolTypeFlag, Set<string>>;
+    force?: boolean;
+}
+
 export interface OnScopeValidateEvent {
     program: Program;
     scope: Scope;
+    changedFiles?: File[];
+    changedSymbols?: Map<SymbolTypeFlag, Set<string>>;
 }
 
 export interface AfterFileTranspileEvent<TFile extends File = File> {
@@ -764,7 +784,7 @@ export interface GetTypeOptions {
 }
 
 export class TypeChainEntry {
-    constructor(public name: string, public type: BscType, public range: Range, public separatorToken: Token = createToken(TokenKind.Dot)) {
+    constructor(public name: string, public type: BscType, public flags: SymbolTypeFlag, public range: Range, public separatorToken: Token = createToken(TokenKind.Dot)) {
     }
     get isResolved() {
         return this.type?.isResolvable();
@@ -783,6 +803,7 @@ export interface TypeChainProcessResult {
 export interface TypeCompatibilityData {
     missingFields?: { name: string; expectedType: BscType }[];
     fieldMismatches?: { name: string; expectedType: BscType; actualType: BscType }[];
+    depth?: number;
 }
 
 export interface NamespaceContainer {
diff --git a/src/parser/Expression.ts b/src/parser/Expression.ts
index 10fa0253..42abb2fb 100644
--- a/src/parser/Expression.ts
+++ b/src/parser/Expression.ts
@@ -341,7 +341,7 @@ export class FunctionExpression extends Expression implements TypedefProvider {
         if (funcName) {
             resultType.setName(funcName);
         }
-        options.typeChain?.push(new TypeChainEntry(funcName, resultType, this.range));
+        options.typeChain?.push(new TypeChainEntry(funcName, resultType, options.flags, this.range));
         return resultType;
     }
 }
@@ -363,7 +363,7 @@ export class FunctionParameterExpression extends Expression {
         const paramType = this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime, typeChain: undefined }) ??
             this.defaultValue?.getType({ ...options, flags: SymbolTypeFlag.runtime, typeChain: undefined }) ??
             DynamicType.instance;
-        options.typeChain?.push(new TypeChainEntry(this.name.text, paramType, this.range));
+        options.typeChain?.push(new TypeChainEntry(this.name.text, paramType, options.flags, this.range));
         return paramType;
     }
 
@@ -466,7 +466,7 @@ export class DottedGetExpression extends Expression {
     getType(options: GetTypeOptions) {
         const objType = this.obj?.getType(options);
         const result = objType?.getMemberType(this.name?.text, options);
-        options.typeChain?.push(new TypeChainEntry(this.name?.text, result, this.name?.range ?? this.range));
+        options.typeChain?.push(new TypeChainEntry(this.name?.text, result, options.flags, this.name?.range ?? this.range));
         if (result || options.flags & SymbolTypeFlag.typetime) {
             // All types should be known at typetime
             return result;
@@ -962,7 +962,7 @@ export class VariableExpression extends Expression {
             const symbolTable = this.getSymbolTable();
             resultType = symbolTable?.getSymbolType(nameKey, { ...options, fullName: nameKey, tableProvider: () => this.getSymbolTable() });
         }
-        options.typeChain?.push(new TypeChainEntry(nameKey, resultType, this.range));
+        options.typeChain?.push(new TypeChainEntry(nameKey, resultType, options.flags, this.range));
         return resultType;
     }
 }
@@ -1175,7 +1175,7 @@ export class CallfuncExpression extends Expression {
         const calleeType = this.callee.getType({ ...options, flags: SymbolTypeFlag.runtime });
         if (isComponentType(calleeType) || isReferenceType(calleeType)) {
             const funcType = (calleeType as ComponentType).getCallFuncType(this.methodName.text, options);
-            options.typeChain?.push(new TypeChainEntry(this.methodName.text, funcType, this.methodName.range, createToken(TokenKind.Callfunc)));
+            options.typeChain?.push(new TypeChainEntry(this.methodName.text, funcType, options.flags, this.methodName.range, createToken(TokenKind.Callfunc)));
             if (options.ignoreCall) {
                 result = funcType;
             }
diff --git a/src/parser/Statement.ts b/src/parser/Statement.ts
index 22fe8be4..2d7175a6 100644
--- a/src/parser/Statement.ts
+++ b/src/parser/Statement.ts
@@ -168,7 +168,7 @@ export class AssignmentStatement extends Statement {
         // Note: compound assignments (eg. +=) are internally dealt with via the RHS being a BinaryExpression
         // so this.value will be a BinaryExpression, and BinaryExpressions can figure out their own types
         const rhs = this.value.getType({ ...options, typeChain: undefined });
-        options.typeChain?.push(new TypeChainEntry(this.name.text, rhs, this.name.range));
+        options.typeChain?.push(new TypeChainEntry(this.name.text, rhs, options.flags, this.name.range));
         return rhs;
     }
 }
@@ -416,7 +416,7 @@ export class FunctionStatement extends Statement implements TypedefProvider {
 
     getType(options: GetTypeOptions) {
         const funcExprType = this.func.getType(options);
-        funcExprType.setName(this.name.text);
+        funcExprType.setName(this.name?.text);
         return funcExprType;
     }
 }
@@ -1524,7 +1524,7 @@ export class InterfaceStatement extends Statement implements TypedefProvider {
             const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
             resultType.addMember(statement?.tokens.name?.text, { definingNode: statement }, memberType, SymbolTypeFlag.runtime);
         }
-        options.typeChain?.push(new TypeChainEntry(this.getName(ParseMode.BrighterScript), resultType, this.range));
+        options.typeChain?.push(new TypeChainEntry(this.getName(ParseMode.BrighterScript), resultType, options.flags, this.range));
         return resultType;
     }
 }
@@ -1722,7 +1722,7 @@ export class InterfaceMethodStatement extends Statement implements TypedefProvid
         }
         let funcName = this.getName(ParseMode.BrighterScript);
         resultType.setName(funcName);
-        options.typeChain?.push(new TypeChainEntry(resultType.name, resultType, this.range));
+        options.typeChain?.push(new TypeChainEntry(resultType.name, resultType, options.flags, this.range));
         return resultType;
     }
 }
@@ -2126,7 +2126,7 @@ export class ClassStatement extends Statement implements TypedefProvider {
             const fieldType = statement.getType({ ...options, typeChain: undefined }); //no typechain needed
             resultType.addMember(statement?.name?.text, { definingNode: statement }, fieldType, SymbolTypeFlag.runtime);
         }
-        options.typeChain?.push(new TypeChainEntry(resultType.name, resultType, this.range));
+        options.typeChain?.push(new TypeChainEntry(resultType.name, resultType, options.flags, this.range));
         return resultType;
     }
 }
diff --git a/src/types/BscType.ts b/src/types/BscType.ts
index 7260039e..842aee2a 100644
--- a/src/types/BscType.ts
+++ b/src/types/BscType.ts
@@ -3,6 +3,7 @@ import type { SymbolTypeFlag } from '../SymbolTable';
 import { SymbolTable } from '../SymbolTable';
 import { BuiltInInterfaceAdder } from './BuiltInInterfaceAdder';
 import type { ExtraSymbolData, TypeCompatibilityData } from '../interfaces';
+import { isReferenceType } from '../astUtils/reflection';
 
 export abstract class BscType {
 
@@ -67,7 +68,7 @@ export abstract class BscType {
         throw new Error('Method not implemented.');
     }
 
-    isEqual(targetType: BscType): boolean {
+    isEqual(targetType: BscType, data: TypeCompatibilityData = {}): boolean {
         throw new Error('Method not implemented.');
     }
 
@@ -75,9 +76,24 @@ export abstract class BscType {
         let isSuperSet = true;
         data.missingFields ||= [];
         data.fieldMismatches ||= [];
+        data.depth = data.depth ? data.depth + 1 : 1;
+        //data.chain ||= [];
         this.addBuiltInInterfaces();
         targetType.addBuiltInInterfaces();
 
+        if (this === targetType) {
+            return true;
+        }
+
+        if (isReferenceType(targetType) && !targetType.isResolvable()) {
+            // we can't resolve the other type. Assume it does not fail on member checks
+            return true;
+        }
+
+        if (data.depth > 16) {
+            // some sort of circular reference
+            return false;
+        }
         const mySymbols = this.getMemberTable()?.getAllSymbols(flags);
         for (const memberSymbol of mySymbols) {
             const targetTypesOfSymbol = targetType.getMemberTable()
@@ -92,7 +108,8 @@ export abstract class BscType {
                         if (!acc) {
                             return acc;
                         }
-                        const myMemberAllowsTargetType = memberSymbol.type.isTypeCompatible(typeOfTargetSymbol);
+
+                        const myMemberAllowsTargetType = memberSymbol.type.isTypeCompatible(typeOfTargetSymbol, { depth: data.depth });
                         if (!myMemberAllowsTargetType) {
                             data.fieldMismatches.push({ name: memberSymbol.name, expectedType: memberSymbol.type, actualType: targetType.getMemberType(memberSymbol.name, { flags: flags }) });
                         }
@@ -101,6 +118,7 @@ export abstract class BscType {
             }
 
         }
+        data.depth = 0;
         return isSuperSet;
     }
 
diff --git a/src/types/BuiltInInterfaceAdder.spec.ts b/src/types/BuiltInInterfaceAdder.spec.ts
index d868b957..3f5749d3 100644
--- a/src/types/BuiltInInterfaceAdder.spec.ts
+++ b/src/types/BuiltInInterfaceAdder.spec.ts
@@ -100,6 +100,14 @@ describe('BuiltInInterfaceAdder', () => {
         expectTypeToBe(myType.getMemberType('clear', { flags: SymbolTypeFlag.runtime }), BooleanType);
     });
 
+    it('should not include members that have already been overridded', () => {
+        const myType = new ClassType('Klass');
+        myType.addMember('clear', null, new BooleanType(), SymbolTypeFlag.runtime);
+        BuiltInInterfaceAdder.addBuiltInInterfacesToType(myType);
+        const myMembers = myType.getMemberTable().getAllSymbols(SymbolTypeFlag.runtime);
+        expect(myMembers.filter(member => member.name.toLowerCase() === 'clear').length).to.eq(1);
+    });
+
     it('should only add built ins to original ancestor', () => {
         const sup = new ClassType('SuperKlass');
         const sub = new ClassType('Klass', sup);
diff --git a/src/types/BuiltInInterfaceAdder.ts b/src/types/BuiltInInterfaceAdder.ts
index cda03994..38a29f1a 100644
--- a/src/types/BuiltInInterfaceAdder.ts
+++ b/src/types/BuiltInInterfaceAdder.ts
@@ -28,13 +28,15 @@ export class BuiltInInterfaceAdder {
     static getLookupTable: () => SymbolTable;
 
     static addBuiltInInterfacesToType(thisType: BscType, overrides?: Map<string, BuiltInInterfaceOverride>) {
-        const memberTable = thisType.getBuiltInMemberTable();
-        if (!memberTable) {
+        const builtInMemberTable = thisType.getBuiltInMemberTable();
+        if (!builtInMemberTable) {
             // no memberTable to add to
             // this could be because it's a class that has a parent
             // the original ancestor should get the built ins
             return;
         }
+        //const realMemberTable = thisType.getMemberTable();
+        //const checkForExistingMembers = realMemberTable && realMemberTable !== builtInMemberTable;
         const builtInComponent = this.getMatchingRokuComponent(thisType);
         if (!builtInComponent) {
             // TODO: Perhaps have error here, but docs have some references to unknown types
@@ -51,11 +53,11 @@ export class BuiltInInterfaceAdder {
             const ifaceData = (interfaces[lowerIfaceName] ?? events[lowerIfaceName]) as BRSInterfaceData;
             for (const method of ifaceData.methods ?? []) {
                 const methodFuncType = this.buildMethodFromDocData(method, overrides);
-                memberTable.addSymbol(method.name, { description: method.description, completionPriority: 1 }, methodFuncType, SymbolTypeFlag.runtime);
+                builtInMemberTable.addSymbol(method.name, { description: method.description, completionPriority: 1 }, methodFuncType, SymbolTypeFlag.runtime);
             }
             for (const property of ifaceData.properties ?? []) {
                 const override = overrides?.get(property.name.toLowerCase());
-                memberTable.addSymbol(property.name, { description: property.description, completionPriority: 1 }, override?.type ?? this.getPrimitiveType(property.type), SymbolTypeFlag.runtime);
+                builtInMemberTable.addSymbol(property.name, { description: property.description, completionPriority: 1 }, override?.type ?? this.getPrimitiveType(property.type), SymbolTypeFlag.runtime);
             }
         }
     }
diff --git a/src/types/ClassType.spec.ts b/src/types/ClassType.spec.ts
index a6556935..38c9b51e 100644
--- a/src/types/ClassType.spec.ts
+++ b/src/types/ClassType.spec.ts
@@ -49,17 +49,18 @@ describe('ClassType', () => {
         const myTable = new SymbolTable('test');
         const futureSuperKlass = new ReferenceType('SuperKlass', 'SuperKlass', SymbolTypeFlag.typetime, () => myTable);
         const subKlass = new ClassType('SubKlass', futureSuperKlass);
-        expect(subKlass.isResolvable()).to.be.false;
+        expect(subKlass.isResolvable()).to.be.true;
+        expect(subKlass.parentType?.isResolvable()).to.be.false;
         const superKlass = new ClassType('SuperKlass');
         myTable.addSymbol('SuperKlass', null, superKlass, SymbolTypeFlag.typetime);
-        expect(subKlass.isResolvable()).to.be.true;
+        expect(subKlass.parentType.isResolvable()).to.be.true;
     });
 
     it('allows members of future super classes to be resolved', () => {
         const myTable = new SymbolTable('test');
         const futureSuperKlass = new ReferenceType('SuperKlass', 'SuperKlass', SymbolTypeFlag.typetime, () => myTable);
         const subKlass = new ClassType('SubKlass', futureSuperKlass);
-        expect(subKlass.isResolvable()).to.be.false;
+        expect(subKlass.parentType.isResolvable()).to.be.false;
         const futureTitleType = subKlass.getMemberType('title', { flags: SymbolTypeFlag.runtime });
         expect(isReferenceType(futureTitleType)).to.be.true;
         expect(futureTitleType.isResolvable()).to.be.false;
diff --git a/src/types/ClassType.ts b/src/types/ClassType.ts
index bb98d25f..f5ddb6c9 100644
--- a/src/types/ClassType.ts
+++ b/src/types/ClassType.ts
@@ -5,18 +5,19 @@ import type { BscType } from './BscType';
 import { BscTypeKind } from './BscTypeKind';
 import { BuiltInInterfaceAdder } from './BuiltInInterfaceAdder';
 import { InheritableType } from './InheritableType';
+import type { ReferenceType } from './ReferenceType';
 import { isUnionTypeCompatible } from './helpers';
 
 export class ClassType extends InheritableType {
 
-    constructor(public name: string, public readonly superClass?: BscType) {
+    constructor(public name: string, public readonly superClass?: ClassType | ReferenceType) {
         super(name, superClass);
     }
 
     public readonly kind = BscTypeKind.ClassType;
 
     public isTypeCompatible(targetType: BscType, data?: TypeCompatibilityData) {
-        if (this.isEqual(targetType)) {
+        if (this.isEqual(targetType, data)) {
             return true;
         } else if (isDynamicType(targetType) ||
             isObjectType(targetType) ||
@@ -28,8 +29,11 @@ export class ClassType extends InheritableType {
         return false;
     }
 
-    isEqual(targetType: BscType): boolean {
-        return isClassType(targetType) && this.name.toLowerCase() === targetType.name.toLowerCase();
+    isEqual(targetType: BscType, data?: TypeCompatibilityData): boolean {
+        if (targetType === this) {
+            return true;
+        }
+        return isClassType(targetType) && super.isEqual(targetType, data);
     }
 
     private builtInMemberTable: SymbolTable;
diff --git a/src/types/EnumType.ts b/src/types/EnumType.ts
index 1b5d3966..5199027a 100644
--- a/src/types/EnumType.ts
+++ b/src/types/EnumType.ts
@@ -1,3 +1,4 @@
+import { SymbolTypeFlag } from '../SymbolTable';
 import { isDynamicType, isEnumMemberType, isEnumType, isObjectType } from '../astUtils/reflection';
 import type { TypeCompatibilityData } from '../interfaces';
 import { BscType } from './BscType';
@@ -36,8 +37,12 @@ export class EnumType extends BscType {
         return 'dynamic';
     }
 
-    public isEqual(targetType: BscType): boolean {
-        return isEnumType(targetType) && targetType?.name.toLowerCase() === this.name.toLowerCase();
+    public isEqual(targetType: BscType, data?: TypeCompatibilityData): boolean {
+        return isEnumType(targetType) &&
+            targetType?.name.toLowerCase() === this.name.toLowerCase() &&
+            this.underlyingType.isEqual(targetType.underlyingType, data) &&
+            this.checkCompatibilityBasedOnMembers(targetType, SymbolTypeFlag.runtime, data) &&
+            targetType.checkCompatibilityBasedOnMembers(this, SymbolTypeFlag.runtime, data);
     }
 
     public get defaultMemberType() {
diff --git a/src/types/InheritableType.ts b/src/types/InheritableType.ts
index cf9b2c55..fe32d9bd 100644
--- a/src/types/InheritableType.ts
+++ b/src/types/InheritableType.ts
@@ -1,11 +1,12 @@
-import type { GetTypeOptions } from '../interfaces';
-import { isInheritableType } from '../astUtils/reflection';
-import type { SymbolTypeFlag } from '../SymbolTable';
+import type { GetTypeOptions, TypeCompatibilityData } from '../interfaces';
+import { isInheritableType, isReferenceType } from '../astUtils/reflection';
+import { SymbolTypeFlag } from '../SymbolTable';
 import { BscType } from './BscType';
+import type { ReferenceType } from './ReferenceType';
 
 export abstract class InheritableType extends BscType {
 
-    constructor(public name: string, public readonly parentType?: BscType) {
+    constructor(public name: string, public readonly parentType?: InheritableType | ReferenceType) {
         super(name);
         if (parentType) {
             this.memberTable.pushParentProvider(() => this.parentType.memberTable);
@@ -24,10 +25,6 @@ export abstract class InheritableType extends BscType {
         return 'dynamic';
     }
 
-    isResolvable(): boolean {
-        return this.parentType ? this.parentType.isResolvable() : true;
-    }
-
     protected getAncestorTypeList(): InheritableType[] {
         const ancestors = [];
         let currentParentType = this.parentType;
@@ -88,5 +85,72 @@ export abstract class InheritableType extends BscType {
         }
         return result + '}';
     }
+
+    isEqual(targetType: BscType, data: TypeCompatibilityData = {}): boolean {
+        if (this === targetType) {
+            return true;
+        }
+        if (isReferenceType(targetType)) {
+            const lowerTargetName = (targetType.memberKey ?? targetType.fullName).toLowerCase();
+            const myLowerName = this.name.toLowerCase();
+
+            if (myLowerName === lowerTargetName) {
+                return true;
+            }
+            //check non-namespaced version
+            if (myLowerName.split('.').reverse()[0] === lowerTargetName) {
+                return true;
+            }
+        }
+        if (!isInheritableType(targetType)) {
+            return false;
+        }
+        if (!targetType) {
+            return false;
+        }
+        if (this === targetType) {
+            return true;
+        }
+        if (this.isAncestorUnresolvedReferenceType() || targetType.isAncestorUnresolvedReferenceType()) {
+            return this.name.toLowerCase() === targetType.name?.toLowerCase() &&
+                this.isParentTypeEqual(targetType, data);
+        }
+        return this.name.toLowerCase() === targetType.name?.toLowerCase() &&
+            this.isParentTypeEqual(targetType, data) &&
+            this.checkCompatibilityBasedOnMembers(targetType, SymbolTypeFlag.runtime, data) &&
+            targetType.checkCompatibilityBasedOnMembers(this, SymbolTypeFlag.runtime, data);
+    }
+
+    protected isParentTypeEqual(targetType: BscType, data?: TypeCompatibilityData): boolean {
+        if (isInheritableType(targetType)) {
+            const targetParent = targetType.parentType;
+            if (this.parentType && !targetParent) {
+                return false;
+            } else if (!this.parentType && !targetParent) {
+                return true;
+            } else if (!this.parentType && targetParent) {
+                return false;
+            }
+            if (isReferenceType(targetParent) || isReferenceType(this.parentType)) {
+                let thisParentName = isReferenceType(this.parentType) ? this.parentType.memberKey ?? this.parentType.fullName : this.parentType.name;
+                let targetParentName = isReferenceType(targetParent) ? targetParent.memberKey ?? targetParent.fullName : targetParent.name;
+                return thisParentName.toLowerCase() === targetParentName.toLowerCase();
+            }
+            return this.parentType.isEqual(targetParent, data);
+        }
+        return false;
+    }
+
+    protected isAncestorUnresolvedReferenceType() {
+        let p = this as InheritableType;
+        while (p) {
+            if (isReferenceType(p) && !p.isResolvable()) {
+                return true;
+            }
+            p = (p as any).parentType;
+
+        }
+        return false;
+    }
 }
 
diff --git a/src/types/InterfaceType.ts b/src/types/InterfaceType.ts
index d076dd3f..efde364c 100644
--- a/src/types/InterfaceType.ts
+++ b/src/types/InterfaceType.ts
@@ -5,11 +5,12 @@ import type { BscType } from './BscType';
 import { BscTypeKind } from './BscTypeKind';
 import { InheritableType } from './InheritableType';
 import { isUnionTypeCompatible } from './helpers';
+import type { ReferenceType } from './ReferenceType';
 
 export class InterfaceType extends InheritableType {
     public constructor(
         public name: string,
-        public readonly superInterface?: BscType
+        public readonly superInterface?: InterfaceType | ReferenceType
     ) {
         super(name, superInterface);
     }
@@ -20,8 +21,8 @@ export class InterfaceType extends InheritableType {
         if (isDynamicType(targetType) || isObjectType(targetType) || isUnionTypeCompatible(this, targetType, data)) {
             return true;
         }
-        if (this.isEqual(targetType)) {
-            return true;
+        if (isInterfaceType(targetType)) {
+            return this.checkCompatibilityBasedOnMembers(targetType, SymbolTypeFlag.runtime, data);
         }
         const ancestorTypes = this.getAncestorTypeList();
         if (ancestorTypes?.find(ancestorType => ancestorType.isEqual(targetType))) {
@@ -33,7 +34,7 @@ export class InterfaceType extends InheritableType {
     /**
      *  Is this the exact same interface as the target?
      */
-    isEqual(targetType: BscType): boolean {
-        return isInterfaceType(targetType) && this.name.toLowerCase() === targetType.name.toLowerCase();
+    isEqual(targetType: BscType, data?: TypeCompatibilityData): boolean {
+        return isInterfaceType(targetType) && super.isEqual(targetType, data);
     }
 }
diff --git a/src/types/ReferenceType.ts b/src/types/ReferenceType.ts
index a30ef8b0..1592e1d8 100644
--- a/src/types/ReferenceType.ts
+++ b/src/types/ReferenceType.ts
@@ -1,4 +1,4 @@
-import type { GetTypeOptions, TypeChainEntry } from '../interfaces';
+import type { GetTypeOptions, TypeChainEntry, TypeCompatibilityData } from '../interfaces';
 import type { GetSymbolTypeOptions, SymbolTypeGetterProvider } from '../SymbolTable';
 import type { SymbolTypeFlag } from '../SymbolTable';
 import { isAnyReferenceType, isComponentType, isDynamicType, isReferenceType } from '../astUtils/reflection';
@@ -57,15 +57,42 @@ export class ReferenceType extends BscType {
                 if (propName === 'isEqual') {
                     //Need to be able to check equality without resolution, because resolution need to check equality
                     //To see if you need to make a UnionType
-                    return (targetType: BscType) => {
+                    return (targetType: BscType, data?: TypeCompatibilityData) => {
+                        if (!targetType) {
+                            return false;
+                        }
+                        const resolvedType = this.resolve();
+                        let equal = false;
+                        if (resolvedType && !isReferenceType(resolvedType)) {
+                            equal = resolvedType.isEqual(targetType, data);
+                        } else if (isReferenceType(targetType)) {
+                            equal = this.fullName.toLowerCase() === targetType.fullName.toLowerCase() &&
+                                (this.tableProvider === targetType.tableProvider ||
+                                    this.tableProvider().name === targetType.tableProvider().name);
+                        } else {
+                            equal = targetType.isEqual(this, data);
+                        }
+                        return equal;
+                    };
+                }
+                if (propName === 'isTypeCompatible') {
+                    //Need to be able to check equality without resolution, because resolution need to check equality
+                    //To see if you need to make a UnionType
+                    return (targetType: BscType, data?: TypeCompatibilityData) => {
+                        if (!targetType) {
+                            return false;
+                        }
+                        if (isDynamicType(targetType)) {
+                            return true;
+                        }
                         const resolvedType = this.resolve();
                         if (resolvedType && !isReferenceType(resolvedType)) {
-                            return resolvedType.isEqual(targetType);
+                            return resolvedType.isTypeCompatible(targetType, data);
                         } else if (isReferenceType(targetType)) {
                             return this.fullName.toLowerCase() === targetType.fullName.toLowerCase() &&
                                 this.tableProvider === targetType.tableProvider;
                         }
-                        return targetType.isEqual(this);
+                        return targetType.isTypeCompatible(this, data);
                     };
                 }
 
@@ -245,6 +272,7 @@ export class ReferenceType extends BscType {
 
     private futureMemberTableProvider = () => {
         return {
+            name: `FutureMemberTableProvider: '${this.__identifier}'`,
             getSymbolType: (innerName: string, innerOptions: GetTypeOptions) => {
                 const resolvedType = this.resolve();
                 if (resolvedType) {
@@ -262,6 +290,7 @@ export class ReferenceType extends BscType {
 
     private futureCallFuncMemberTableProvider = () => {
         return {
+            name: `FutureCallFuncMemberTableProvider: '${this.__identifier}'`,
             getSymbolType: (innerName: string, innerOptions: GetTypeOptions) => {
                 const resolvedType = this.resolve();
                 if (isComponentType(resolvedType)) {
@@ -309,6 +338,7 @@ export class TypePropertyReferenceType extends BscType {
                             const fullMemberName = this.outerType.toString() + '.' + memberName;
                             return new ReferenceType(memberName, fullMemberName, options.flags, () => {
                                 return {
+                                    name: `TypePropertyReferenceType : '${fullMemberName}'`,
                                     getSymbolType: (innerName: string, innerOptions: GetTypeOptions) => {
                                         return this.outerType?.[this.propertyName]?.getMemberType(innerName, innerOptions);
                                     },
diff --git a/src/types/TypedFunctionType.ts b/src/types/TypedFunctionType.ts
index 8fd4547e..1061e041 100644
--- a/src/types/TypedFunctionType.ts
+++ b/src/types/TypedFunctionType.ts
@@ -46,7 +46,7 @@ export class TypedFunctionType extends BaseFunctionType {
         return this;
     }
 
-    public isTypeCompatible(targetType: BscType, data?: TypeCompatibilityData) {
+    public isTypeCompatible(targetType: BscType, data: TypeCompatibilityData = {}) {
         if (
             isDynamicType(targetType) ||
             isObjectType(targetType) ||
@@ -55,7 +55,7 @@ export class TypedFunctionType extends BaseFunctionType {
             return true;
         }
         if (isTypedFunctionType(targetType)) {
-            return this.checkParamsAndReturnValue(targetType, true, (t1, t2) => t1.isTypeCompatible(t2));
+            return this.checkParamsAndReturnValue(targetType, true, (t1, t2, d) => t1.isTypeCompatible(t2, d), data);
         }
         return false;
     }
@@ -86,7 +86,7 @@ export class TypedFunctionType extends BaseFunctionType {
         return false;
     }
 
-    private checkParamsAndReturnValue(targetType: TypedFunctionType, allowOptionalParamDifferences: boolean, predicate: (type1: BscType, type2: BscType) => boolean) {
+    private checkParamsAndReturnValue(targetType: TypedFunctionType, allowOptionalParamDifferences: boolean, predicate: (type1: BscType, type2: BscType, data: TypeCompatibilityData) => boolean, data?: TypeCompatibilityData) {
         //compare all parameters
         let len = Math.max(this.params.length, targetType.params.length);
         for (let i = 0; i < len; i++) {
@@ -97,7 +97,7 @@ export class TypedFunctionType extends BaseFunctionType {
                 break;
             }
 
-            if (!myParam || !targetParam || !predicate(targetParam.type, myParam.type)) {
+            if (!myParam || !targetParam || !predicate(targetParam.type, myParam.type, data)) {
                 return false;
             }
             if (!allowOptionalParamDifferences && myParam.isOptional !== targetParam.isOptional) {
@@ -107,7 +107,7 @@ export class TypedFunctionType extends BaseFunctionType {
             }
         }
         //compare return type
-        if (!this.returnType || !targetType.returnType || !predicate(this.returnType, targetType.returnType)) {
+        if (!this.returnType || !targetType.returnType || !predicate(this.returnType, targetType.returnType, data)) {
             return false;
         }
         if (this.isVariadic !== targetType.isVariadic) {
diff --git a/src/types/UnionType.ts b/src/types/UnionType.ts
index 8474cf6f..94a32095 100644
--- a/src/types/UnionType.ts
+++ b/src/types/UnionType.ts
@@ -45,6 +45,7 @@ export class UnionType extends BscType {
             // so instead, create reference type that will
             return new ReferenceType(name, name, options.flags, () => {
                 return {
+                    name: `UnionType MemberTable: '${this.__identifier}'`,
                     getSymbolType: (innerName: string, innerOptions: GetTypeOptions) => {
                         return getUniqueType(findTypeUnion(this.getMemberTypeFromInnerTypes(name, options)), unionTypeFactory);
                     },
diff --git a/src/util.spec.ts b/src/util.spec.ts
index 68af16d7..e210925d 100644
--- a/src/util.spec.ts
+++ b/src/util.spec.ts
@@ -408,7 +408,7 @@ describe('util', () => {
         });
 
         it('sets default value for bslibDestinationDir', () => {
-            expect(util.normalizeConfig(<any>{ }).bslibDestinationDir).to.equal('source');
+            expect(util.normalizeConfig(<any>{}).bslibDestinationDir).to.equal('source');
         });
 
         it('strips leading and/or trailing slashes from bslibDestinationDir', () => {
@@ -916,9 +916,9 @@ describe('util', () => {
     describe('processTypeChain', () => {
         it('should  find the correct details in a list of type resolutions', () => {
             const chain = [
-                new TypeChainEntry('AlphaNamespace', new NamespaceType('Alpha'), util.createRange(1, 1, 2, 2)),
-                new TypeChainEntry('BetaProp', new ClassType('Beta'), util.createRange(2, 2, 3, 3)),
-                new TypeChainEntry('CharlieProp', new ReferenceType('Charlie', 'Alpha.Beta.CharlieProp', SymbolTypeFlag.runtime, () => null), util.createRange(3, 3, 4, 4))
+                new TypeChainEntry('AlphaNamespace', new NamespaceType('Alpha'), SymbolTypeFlag.runtime, util.createRange(1, 1, 2, 2)),
+                new TypeChainEntry('BetaProp', new ClassType('Beta'), SymbolTypeFlag.runtime, util.createRange(2, 2, 3, 3)),
+                new TypeChainEntry('CharlieProp', new ReferenceType('Charlie', 'Alpha.Beta.CharlieProp', SymbolTypeFlag.runtime, () => null), SymbolTypeFlag.runtime, util.createRange(3, 3, 4, 4))
             ];
 
             const result = util.processTypeChain(chain);
@@ -931,8 +931,8 @@ describe('util', () => {
 
         it('respects the separatorToken', () => {
             const chain = [
-                new TypeChainEntry('roSGNodeCustom', new ComponentType('Custom'), util.createRange(1, 1, 2, 2)),
-                new TypeChainEntry('someCallFunc', new TypedFunctionType(VoidType.instance), util.createRange(2, 2, 3, 3), createToken(TokenKind.Callfunc))
+                new TypeChainEntry('roSGNodeCustom', new ComponentType('Custom'), SymbolTypeFlag.runtime, util.createRange(1, 1, 2, 2)),
+                new TypeChainEntry('someCallFunc', new TypedFunctionType(VoidType.instance), SymbolTypeFlag.runtime, util.createRange(2, 2, 3, 3), createToken(TokenKind.Callfunc))
             ];
 
             const result = util.processTypeChain(chain);
diff --git a/src/util.ts b/src/util.ts
index ee9a2464..ce295b60 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -43,6 +43,7 @@ import { AssociativeArrayType } from './types/AssociativeArrayType';
 import { ComponentType } from './types/ComponentType';
 import { MAX_RELATED_INFOS_COUNT } from './diagnosticUtils';
 import { BinaryOperatorReferenceType } from './types/ReferenceType';
+import type { UnresolvedSymbol } from './AstValidationSegmenter';
 
 export class Util {
     public clearConsole() {
@@ -1874,6 +1875,33 @@ 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 += '.';
+            }
+            nameSoFar += tce.name.toLowerCase();
+            possibleOriginalSymbolNamesLower.push(nameSoFar);
+        }
+        const possibleNamespace = symbol.containingNamespaces?.join('.') ?? '';
+
+        for (const possibleNameLower of possibleOriginalSymbolNamesLower) {
+            if (symbolLowerNameSet.has(possibleNameLower)) {
+                return true;
+            }
+            if (possibleNamespace) {
+                const fullName = possibleNamespace + '.' + possibleNameLower;
+                if (symbolLowerNameSet.has(fullName.toLowerCase())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     public truncate<T>(options: {
         leadingText: string;
         items: T[];


Test patch:
diff --git a/src/parser/tests/statement/ConstStatement.spec.ts b/src/parser/tests/statement/ConstStatement.spec.ts
index 2f15ec02..5e3704c5 100644
--- a/src/parser/tests/statement/ConstStatement.spec.ts
+++ b/src/parser/tests/statement/ConstStatement.spec.ts
@@ -198,10 +198,10 @@ describe('ConstStatement', () => {
                     log()
                 end sub
             `);
-
+            program.validate();
+            // log(|)
             expectCompletionsIncludes(
-                // log(|)
-                program.getCompletions('source/main.bs', util.createPosition(3, 24)),
+                program.getCompletions('source/main.bs', util.createPosition(3, 34)),
                 [{
                     label: 'API_KEY',
                     kind: CompletionItemKind.Constant
@@ -246,6 +246,7 @@ describe('ConstStatement', () => {
                     log(constants.)
                 end sub
             `);
+            program.validate();
 
             expectCompletionsIncludes(
                 // log(|)