diff --git a/server/src/UC/Symbols/ArchetypeSymbol.ts b/server/src/UC/Symbols/ArchetypeSymbol.ts index ad6cc81f..31cf4d79 100644 --- a/server/src/UC/Symbols/ArchetypeSymbol.ts +++ b/server/src/UC/Symbols/ArchetypeSymbol.ts @@ -3,6 +3,7 @@ import { Name } from '../name'; import { SymbolWalker } from '../symbolWalker'; import { ModifierFlags, + UCClassSymbol, UCFieldSymbol, UCObjectSymbol, UCStructSymbol, @@ -17,9 +18,11 @@ import { export class UCArchetypeSymbol extends UCStructSymbol { override kind = UCSymbolKind.Archetype; declare outer: UCObjectSymbol; + declare super?: UCClassSymbol; override modifiers = ModifierFlags.ReadOnly; public overriddenArchetype?: UCArchetypeSymbol; + public document: UCDocument; override getTypeKind() { return UCTypeKind.Object; diff --git a/server/src/UC/Symbols/ClassSymbol.ts b/server/src/UC/Symbols/ClassSymbol.ts index ff4f8db7..e599c6fd 100644 --- a/server/src/UC/Symbols/ClassSymbol.ts +++ b/server/src/UC/Symbols/ClassSymbol.ts @@ -1,13 +1,13 @@ import { Position } from 'vscode-languageserver-types'; import { UCDocument } from '../document'; -import { intersectsWith, intersectsWithRange } from '../helpers'; import { Name } from '../name'; import { SymbolWalker } from '../symbolWalker'; import { ISymbol, ITypeSymbol, ModifierFlags, + UCArchetypeSymbol, UCFieldSymbol, UCObjectTypeSymbol, UCPackage, @@ -22,7 +22,6 @@ export enum ClassModifierFlags { Interface = 1 << 0, } -// TODO: Derive this class as UCInterfaceSymbol export class UCClassSymbol extends UCStructSymbol { static readonly allowedKindsMask = 1 << UCSymbolKind.Const | 1 << UCSymbolKind.Enum @@ -46,6 +45,12 @@ export class UCClassSymbol extends UCStructSymbol { public dependsOnTypes?: UCObjectTypeSymbol[]; public implementsTypes?: (UCQualifiedTypeSymbol | UCObjectTypeSymbol)[]; + /** + * (UC3) A generated symbol to hold onto object declarations (archetypes) + * If archetypes are not available, it will be referencing the outer class or struct. + */ + public defaults: UCStructSymbol | UCArchetypeSymbol; + override getTypeKind() { return UCTypeKind.Object; } diff --git a/server/src/UC/Symbols/DefaultPropertiesBlock.ts b/server/src/UC/Symbols/DefaultPropertiesBlock.ts index ea267982..128541c1 100644 --- a/server/src/UC/Symbols/DefaultPropertiesBlock.ts +++ b/server/src/UC/Symbols/DefaultPropertiesBlock.ts @@ -1,26 +1,20 @@ import { SymbolWalker } from '../symbolWalker'; import { - isArchetypeSymbol, ModifierFlags, UCFieldSymbol, UCStructSymbol, UCSymbolKind + getContext, + isArchetypeSymbol, ModifierFlags, UCClassSymbol, UCStructSymbol, UCSymbolKind } from './'; export class UCDefaultPropertiesBlock extends UCStructSymbol { override kind = UCSymbolKind.DefaultPropertiesBlock; override modifiers = ModifierFlags.NoDeclaration; - // Generated symbol (not passed to a visitor) - // Set to either the document's class or a generated Default_ClassName archetype. - public default: UCStructSymbol; - override getTooltip(): string { - return `(${this.id.name.text.toLowerCase()}) ${this.default.getTooltip()}`; - } - - override addSymbol(symbol: UCFieldSymbol): number | undefined { - const r = super.addSymbol(symbol); - if (isArchetypeSymbol(symbol)) { - symbol.outer = this.default; + const outerClass = getContext(this, UCSymbolKind.Class); + if (outerClass?.defaults && isArchetypeSymbol(outerClass.defaults)) { + return `(${this.id.name.text.toLowerCase()}) ${outerClass.getTooltip()}`; } - return r; + + return `(${this.id.name.text.toLowerCase()})`; } override accept(visitor: SymbolWalker): Result | void { diff --git a/server/src/UC/documentASTWalker.ts b/server/src/UC/documentASTWalker.ts index 402afa93..fbe93424 100644 --- a/server/src/UC/documentASTWalker.ts +++ b/server/src/UC/documentASTWalker.ts @@ -89,6 +89,7 @@ import { import { addHashedSymbol, DEFAULT_RANGE, + getContext, hasNoKind, Identifier, IntrinsicObject, @@ -555,6 +556,32 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor implements break; } } + + if (config.generation === UCGeneration.UC3) { + const defaultId: Identifier = { + name: toName(`Default__${identifier.name.text}`), + range: identifier.range + }; + + const defaults = new UCArchetypeSymbol(defaultId, identifier.range); + defaults.modifiers |= ModifierFlags.Generated; + // the archetype has this symbol as class !! + defaults.super = symbol; + + // Don't register nor hash (in UE these objects are generated in the third pass) + // this.declare(defaults); + + // The 'defaults' archetype is expected to reside in the class's package instead of the usual behavior i.e. the class. + defaults.outer = this.document.classPackage; + + defaults.document = this.document; + + symbol.defaults = defaults; + } else { + // point to self for UC1, UC2 + symbol.defaults = symbol; + } + return symbol; } @@ -1064,7 +1091,6 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor implements }; const range = rangeFromBounds(ctx.start, ctx.stop); const symbol = new UCDefaultPropertiesBlock(identifier, range); - symbol.default = this.scope(); this.declare(symbol, ctx); this.push(symbol); @@ -1073,6 +1099,7 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor implements } finally { this.pop(); } + return symbol; } @@ -1085,30 +1112,17 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor implements const symbol = new UCDefaultPropertiesBlock(identifier, range); this.declare(symbol, ctx); - this.push(symbol); + + const classOuter = getContext(symbol, UCSymbolKind.Class)!; + console.debug(typeof classOuter !== 'undefined', 'expected defaultproperties to be declared in a class scope.'); + + this.push(classOuter.defaults); try { - let defaultObject: UCArchetypeSymbol | undefined; - if (config.generation === UCGeneration.UC3) { - const defaultId: Identifier = { - name: toName(`Default__${this.document.name.text}`), - range: identifier.range - }; - defaultObject = new UCArchetypeSymbol(defaultId, range); - defaultObject.modifiers |= ModifierFlags.Generated; - defaultObject.outer = this.document.classPackage; - defaultObject.super = this.document.class; - this.declare(defaultObject); - symbol.default = defaultObject; - } else { - symbol.default = this.document.class!; - } symbol.block = createBlock(this, ctx.defaultStatement()); - if (defaultObject) { - defaultObject.block = symbol.block; - } } finally { this.pop(); } + return symbol; } @@ -1179,10 +1193,12 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor implements if (classType) { symbol.extendsType = classType; } - + + symbol.document = this.document; + const statement = new UCArchetypeBlockStatement(statementRange); statement.archetypeSymbol = symbol; - + this.declare(symbol, ctx); this.push(symbol); try { @@ -1193,6 +1209,7 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor implements } finally { this.pop(); } + return statement; } @@ -1747,7 +1764,7 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor implements } expression.expression = ctx._expr.accept(this); - + return expression; } diff --git a/server/src/UC/documentCodeIndexer.ts b/server/src/UC/documentCodeIndexer.ts index 0a978cc1..40e2dd1b 100644 --- a/server/src/UC/documentCodeIndexer.ts +++ b/server/src/UC/documentCodeIndexer.ts @@ -2,6 +2,7 @@ import { UCDocument } from './document'; import { UCArchetypeBlockStatement } from './statements'; import { ContextInfo, + getContext, isParamSymbol, isStruct, UCArchetypeSymbol, @@ -14,6 +15,7 @@ import { UCScriptStructSymbol, UCStateSymbol, UCStructSymbol, + UCSymbolKind, } from './Symbols'; import { DefaultSymbolWalker } from './symbolWalker'; @@ -125,9 +127,10 @@ export class DocumentCodeIndexer extends DefaultSymbolWalker { // this.visitStructBase(symbol); if (symbol.block) { - // index in the default context (i.e. the class or generated archetype) - console.assert(typeof symbol.default !== 'undefined', 'symbol.default is undefined'); - symbol.block.index(this.document, symbol.default); + const classOuter = getContext(symbol, UCSymbolKind.Class)!; + + // index in the defaults context (i.e. the class or generated archetype) + symbol.block.index(this.document, classOuter.defaults); } } diff --git a/server/src/UC/documentSymbolTagsBuilder.ts b/server/src/UC/documentSymbolTagsBuilder.ts index c6d48f60..333d957c 100644 --- a/server/src/UC/documentSymbolTagsBuilder.ts +++ b/server/src/UC/documentSymbolTagsBuilder.ts @@ -1,10 +1,10 @@ import { SymbolTag } from 'vscode-languageserver'; -import { UCObjectSymbol } from './Symbols'; +import { ISymbol } from './Symbols'; import { SymbolTagsBuilderVisitor } from './SymbolTagsBuilderVisitor'; const SymbolTagsBuilder = new SymbolTagsBuilderVisitor(); -export function getSymbolTags(symbol: UCObjectSymbol): SymbolTag[] | undefined { +export function getSymbolTags(symbol: ISymbol): SymbolTag[] | undefined { return symbol.accept(SymbolTagsBuilder) as SymbolTag[] | undefined; -} \ No newline at end of file +} diff --git a/server/src/UC/expressions.ts b/server/src/UC/expressions.ts index ab255083..017834a6 100644 --- a/server/src/UC/expressions.ts +++ b/server/src/UC/expressions.ts @@ -801,6 +801,16 @@ export class UCDefaultAssignmentExpression extends UCAssignmentOperatorExpressio } } +/** + * An expression to represent a property assignment in an object declaration i.e `Property=Expression` + * + * e.g. in a 'object declaration' the part `Left=Right` is represented by this expression type + * + * ```UnrealScript + * Begin Object Left=Right + * End Object + * ``` + */ export class UCObjectAttributeExpression extends UCDefaultAssignmentExpression { } diff --git a/server/src/UC/helpers.ts b/server/src/UC/helpers.ts index 5455852b..c2857ab9 100644 --- a/server/src/UC/helpers.ts +++ b/server/src/UC/helpers.ts @@ -11,6 +11,7 @@ import { UCSymbolKind, getContext, hasModifiers, + isArchetypeSymbol, isClass, isField, supportsRef, @@ -259,6 +260,11 @@ export function getSymbol(uri: DocumentUri, position: Position): ISymbol | undef export function getSymbolDocument(symbol: ISymbol): UCDocument | undefined { console.assert(typeof symbol !== 'undefined'); + // An archetype's outer in UE3 resides in the package instead of the class, so we have to handle this special case. + if (isArchetypeSymbol(symbol)) { + return symbol.document; + } + const documentClass = getContext(symbol, UCSymbolKind.Class); const document = documentClass && getDocumentById(documentClass.id.name); return document; diff --git a/server/src/UC/statements.ts b/server/src/UC/statements.ts index 36cf9137..8dd31261 100644 --- a/server/src/UC/statements.ts +++ b/server/src/UC/statements.ts @@ -1,18 +1,20 @@ /* eslint-disable prefer-rest-params */ /* eslint-disable prefer-spread */ -import { Position, Range } from 'vscode-languageserver'; +import { Location, Position, Range } from 'vscode-languageserver'; import { UCDocument } from './document'; import { IExpression } from './expressions'; import { intersectsWith } from './helpers'; import { addHashedSymbol, - ContextInfo, Identifier, INode, ISymbol, IWithInnerSymbols, ObjectsTable, StaticNameType, - UCArchetypeSymbol, UCNodeKind, UCObjectTypeSymbol, UCStructSymbol, + ContextInfo, getContext, Identifier, INode, isArchetypeSymbol, ISymbol, IWithInnerSymbols, ObjectsTable, StaticNameType, + UCArchetypeSymbol, UCClassSymbol, UCNodeKind, UCObjectTypeSymbol, UCStructSymbol, UCSymbolKind } from './Symbols'; import { SymbolWalker } from './symbolWalker'; import { NAME_NONE } from './names'; +import { config, indexDeclarationReference, indexReference } from './indexer'; +import { UCGeneration } from './settings'; export interface IStatement extends INode, IWithInnerSymbols { getSymbolAtPos(position: Position): ISymbol | undefined; @@ -134,26 +136,45 @@ export class UCArchetypeBlockStatement implements IStatement { } getSymbolAtPos(position: Position): ISymbol | undefined { - return this.getContainedSymbolAtPos(position); + return intersectsWith(this.range, position) + ? this.getContainedSymbolAtPos(position) + : undefined; } - getContainedSymbolAtPos(position: Position) { - return this.block?.getSymbolAtPos(position); + getContainedSymbolAtPos(position: Position): ISymbol | undefined { + return this.block?.getSymbolAtPos(position) ?? (intersectsWith(this.archetypeSymbol.id.range, position) ? this.archetypeSymbol : undefined); } - index(document: UCDocument, _context: UCStructSymbol, info?: ContextInfo): void { - // Index the archetype class - this.archetypeSymbol.index(document, _context); + index(document: UCDocument, context: UCStructSymbol, info?: ContextInfo): void { + // Index the archetype's super class + this.archetypeSymbol.index(document, context); if (this.archetypeSymbol.id.name !== NAME_NONE) { // Find the override if no class is specified (extendsType) - if (!this.archetypeSymbol.extendsType && !this.archetypeSymbol.super) { - const overriddenArchetype = ObjectsTable.getSymbol(this.archetypeSymbol.id.name, UCSymbolKind.Archetype); - this.archetypeSymbol.overriddenArchetype = overriddenArchetype; - this.archetypeSymbol.super = overriddenArchetype; + if (config.generation === UCGeneration.UC3 + && !this.archetypeSymbol.extendsType + && !this.archetypeSymbol.super) { + // expecting context to be the 'Defaults__ClassName' archetype, so let's use its 'super' which should be the class it was declared in. + const archetypeOuterClass = context.super && getContext(context.super, UCSymbolKind.Class); + if (archetypeOuterClass && isArchetypeSymbol(archetypeOuterClass.defaults)) { + // Unlike the UnrealScript compiler + // -- we cannot safely find the symbol by using the hash table + // -- because that is sensitive to the compilation order of classes. + for (let parent = archetypeOuterClass.super; parent; parent = parent.super) { + const overriddenArchetype = parent.defaults.getSymbol(this.archetypeSymbol.id.name, UCSymbolKind.Archetype); + if (overriddenArchetype) { + this.archetypeSymbol.overriddenArchetype = overriddenArchetype; + this.archetypeSymbol.super = overriddenArchetype.super; + + // Index a reference to the overriden archetype. + indexReference(overriddenArchetype, document, Location.create(document.uri, this.archetypeSymbol.id.range)); + break; + } + } + } } - - // Allow assignments to find this archetype (in order! If we were to pre-hash these we may incorrectly override the hash table assigned symbol) + + // Allow assignments to find this archetype (by identifier or an object literal) addHashedSymbol(this.archetypeSymbol); }