Skip to content

Commit

Permalink
Re-factor and fix archetype overriding.
Browse files Browse the repository at this point in the history
  • Loading branch information
EliotVU committed May 23, 2024
1 parent cc6e54a commit 8539d64
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 59 deletions.
3 changes: 3 additions & 0 deletions server/src/UC/Symbols/ArchetypeSymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Name } from '../name';
import { SymbolWalker } from '../symbolWalker';
import {
ModifierFlags,
UCClassSymbol,
UCFieldSymbol,
UCObjectSymbol,
UCStructSymbol,
Expand All @@ -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;
Expand Down
9 changes: 7 additions & 2 deletions server/src/UC/Symbols/ClassSymbol.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand All @@ -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;
}
Expand Down
20 changes: 7 additions & 13 deletions server/src/UC/Symbols/DefaultPropertiesBlock.ts
Original file line number Diff line number Diff line change
@@ -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<UCClassSymbol>(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<Result>(visitor: SymbolWalker<Result>): Result | void {
Expand Down
63 changes: 40 additions & 23 deletions server/src/UC/documentASTWalker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import {
import {
addHashedSymbol,
DEFAULT_RANGE,
getContext,
hasNoKind,
Identifier,
IntrinsicObject,
Expand Down Expand Up @@ -555,6 +556,32 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor<any> 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;
}

Expand Down Expand Up @@ -1064,7 +1091,6 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor<any> implements
};
const range = rangeFromBounds(ctx.start, ctx.stop);
const symbol = new UCDefaultPropertiesBlock(identifier, range);
symbol.default = this.scope<UCStructSymbol>();

this.declare(symbol, ctx);
this.push(symbol);
Expand All @@ -1073,6 +1099,7 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor<any> implements
} finally {
this.pop();
}

return symbol;
}

Expand All @@ -1085,30 +1112,17 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor<any> implements
const symbol = new UCDefaultPropertiesBlock(identifier, range);

this.declare(symbol, ctx);
this.push(symbol);

const classOuter = getContext<UCClassSymbol>(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;
}

Expand Down Expand Up @@ -1179,10 +1193,12 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor<any> 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 {
Expand All @@ -1193,6 +1209,7 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor<any> implements
} finally {
this.pop();
}

return statement;
}

Expand Down Expand Up @@ -1747,7 +1764,7 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor<any> implements
}

expression.expression = ctx._expr.accept(this);

return expression;
}

Expand Down
9 changes: 6 additions & 3 deletions server/src/UC/documentCodeIndexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { UCDocument } from './document';
import { UCArchetypeBlockStatement } from './statements';
import {
ContextInfo,
getContext,
isParamSymbol,
isStruct,
UCArchetypeSymbol,
Expand All @@ -14,6 +15,7 @@ import {
UCScriptStructSymbol,
UCStateSymbol,
UCStructSymbol,
UCSymbolKind,
} from './Symbols';
import { DefaultSymbolWalker } from './symbolWalker';

Expand Down Expand Up @@ -125,9 +127,10 @@ export class DocumentCodeIndexer extends DefaultSymbolWalker<undefined> {
// 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<UCClassSymbol>(symbol, UCSymbolKind.Class)!;

// index in the defaults context (i.e. the class or generated archetype)
symbol.block.index(this.document, classOuter.defaults);
}
}

Expand Down
6 changes: 3 additions & 3 deletions server/src/UC/documentSymbolTagsBuilder.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
10 changes: 10 additions & 0 deletions server/src/UC/expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

}
Expand Down
6 changes: 6 additions & 0 deletions server/src/UC/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
UCSymbolKind,
getContext,
hasModifiers,
isArchetypeSymbol,
isClass,
isField,
supportsRef,
Expand Down Expand Up @@ -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<UCClassSymbol>(symbol, UCSymbolKind.Class);
const document = documentClass && getDocumentById(documentClass.id.name);
return document;
Expand Down
51 changes: 36 additions & 15 deletions server/src/UC/statements.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<UCArchetypeSymbol>(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<UCClassSymbol>(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<UCArchetypeSymbol>(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);
}

Expand Down

0 comments on commit 8539d64

Please sign in to comment.