Skip to content

Commit

Permalink
Fix #182; 'Enum.EnumTag' usage as an element dimension.
Browse files Browse the repository at this point in the history
  • Loading branch information
EliotVU committed Jun 17, 2024
1 parent 93dd7ca commit b22a4cd
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 141 deletions.
10 changes: 6 additions & 4 deletions server/src/UC/Symbols/ClassSymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ export class UCClassSymbol extends UCStructSymbol {
}

override index(document: UCDocument, context: UCClassSymbol) {
// !! index before any children (because type references may be dependent on a resolved 'within')
if (this.withinType) {
this.withinType.index(document, context);
this.within ??= this.withinType.getRef<UCClassSymbol>();
}

if (this.dependsOnTypes) {
for (const classTypeRef of this.dependsOnTypes) {
classTypeRef.index(document, context);
Expand All @@ -156,10 +162,6 @@ export class UCClassSymbol extends UCStructSymbol {

super.index(document, context);

if (this.withinType) {
this.withinType.index(document, context);
this.within ??= this.withinType.getRef<UCClassSymbol>();
}
}

override accept<Result>(visitor: SymbolWalker<Result>): Result | void {
Expand Down
6 changes: 3 additions & 3 deletions server/src/UC/Symbols/ScriptStructSymbol.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { UCDocument } from '../document';
import { Name } from '../name';
import { SymbolWalker } from '../symbolWalker';
import { ContextKind, ISymbol, ModifierFlags, UCFieldSymbol, UCStructSymbol, UCSymbolKind, UCTypeKind } from './';
import { ContextKind, ISymbol, ModifierFlags, UCClassSymbol, UCFieldSymbol, UCStructSymbol, UCSymbolKind, UCTypeKind } from './';

export class UCScriptStructSymbol extends UCStructSymbol {
declare outer: UCStructSymbol;
declare outer: UCScriptStructSymbol | UCClassSymbol;

static readonly allowedKindsMask = 1 << UCSymbolKind.Const
| 1 << UCSymbolKind.ScriptStruct
| 1 << UCSymbolKind.Property;
Expand Down
68 changes: 62 additions & 6 deletions server/src/UC/Symbols/StructSymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@ import { Name } from '../name';
import { UCBlock } from '../statements';
import { SymbolWalker } from '../symbolWalker';
import {
areIdentityMatch,
ContextKind,
getContext,
getConversionCost,
Identifier,
IntrinsicObject,
isFunction,
isOperator,
isStateSymbol,
isStruct,
ISymbol,
ISymbolContainer,
ITypeSymbol,
ModifierFlags,
UCBaseOperatorSymbol,
UCBinaryOperatorSymbol,
UCClassSymbol,
UCConversionCost,
UCFieldSymbol,
UCMatchFlags,
Expand Down Expand Up @@ -197,18 +202,69 @@ export class UCStructSymbol extends UCFieldSymbol implements ISymbolContainer<UC
}

/**
* Looks up the @struct's hierarchy for a matching @id
* Finds a super symbol by name.
* @param name the super symbol name.
* @param context any symbol with a defined `super`
* @returns the super if any.
*/
export function findSuperStruct(context: UCStructSymbol, id: Name): UCStructSymbol | undefined {
for (let other = context.super; other; other = other.super) {
if (other.id.name === id) {
return other;
export function findSuperStructSymbol(
context: SuperSymbol,
name: Name
): SuperSymbol | undefined {
for (let scope = context.super; scope; scope = scope.super) {
if (scope.id.name === name) {
return scope;
}
}

return undefined;
}

export function getOperatorsByName<T extends UCBaseOperatorSymbol>(context: UCStructSymbol | undefined, name: Name): T[] {
/**
* Finds a field symbol by name and kind in a provided context.
* The context's outer and super fields will be scanned.
* If all fails, the context's outer most class 'within' will be scanned.
* @param name the field name.
* @param context any symbol with fields to scan.
* @param kind the field kind.
* @returns the field if any.
*/
export function findOuterFieldSymbol<T extends UCFieldSymbol>(
context: UCStructSymbol,
name: Name,
kind?: UCSymbolKind
): T | undefined {
function findByOuter(scope: UCStructSymbol): T | undefined {
for (; isStruct(scope); scope = <UCStructSymbol>scope.outer) {
// TODO: Maybe inline findSuperSymbol to ensure we work with predictable code.
const symbol = scope.findSuperSymbol<T>(name, kind);
if (symbol) {
return symbol;
}
}

return undefined;
}

const symbol = findByOuter(context);
if (symbol) {
return <T>symbol;
}

// Re-try in the outer most class's 'within'
// p.s. may be 'undefined' if resolving within an included .uc file, or an `Interface` class.
const outerClass = getContext<UCClassSymbol>(context, UCSymbolKind.Class);
if (outerClass?.within && !areIdentityMatch(outerClass.within, IntrinsicObject)) {
return <T>findByOuter(outerClass.within);
}

return undefined;
}

export function getOperatorsByName<T extends UCBaseOperatorSymbol>(
context: UCStructSymbol | undefined,
name: Name
): T[] {
let scope: UCStructSymbol | undefined = isFunction(context)
? context.outer as UCStructSymbol
: context;
Expand Down
28 changes: 21 additions & 7 deletions server/src/UC/Symbols/TypeSymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ import {
areIdentityMatch,
tryFindClassSymbol,
tryFindSymbolInPackage,
UCInterfaceSymbol
UCInterfaceSymbol,
findOuterFieldSymbol
} from './';

export const enum UCNodeKind {
Expand Down Expand Up @@ -402,11 +403,6 @@ export class UCObjectTypeSymbol<TBaseType extends ITypeSymbol = ITypeSymbol> imp
symbol = tryFindClassSymbol(id);
break;

// Not applicable
// case UCSymbolKind.Enum:
// symbol = ObjectsTable.getSymbol<UCStructSymbol>(id, UCSymbolKind.Enum);
// break;

// Struct Foo extends context?.id
case UCSymbolKind.ScriptStruct:
// Prioritize parent-inherited structs first
Expand Down Expand Up @@ -455,6 +451,24 @@ export class UCObjectTypeSymbol<TBaseType extends ITypeSymbol = ITypeSymbol> imp
break;
}

// We expect an enum, presumed to be only used outside of code blocks. i.e. `MyVar[MyConstOrMyEnum.MaybeEnumTag]`
// As per the compiler:
// We must prioritize const lookups in the current context
// - its outers and the outer's inherited classes too.
// If not found, look through the 'within' class of the outer most class and repeat.
// - Repeat the same for 'Enum', if all fail then we must lookup the enum in the global table
// - (this lets us pickup an enum declared in a 'dependson' class or a class compiled before)
// - However this will also match enum's declared in any class that is yet-to-be-compiled, even an enum in an unrelated package.
// - Perhaps we can run an analysis post-index to see if the enum is a possible 'dependency'
case UCSymbolKind.Enum: {
symbol = findOuterFieldSymbol(context, id, UCSymbolKind.Const)
?? findOuterFieldSymbol(context, id, UCSymbolKind.Enum)
// Global lookup
?? ObjectsTable.getSymbol(id, UCSymbolKind.Enum);

break;
}

// No expected type, let's do a general (inheritance based) lookup
default:
// Ensure we are working with a valid context, bad user or incomplete code may give us an unsuitable context.
Expand Down Expand Up @@ -881,7 +895,7 @@ export function typesMatch(
if (inputTypeKind === destTypeKind) {
// If we are expecting an assignment to an object that has a class type, then verify that the input class is compatible.
if ((destTypeKind === UCTypeKind.Object ||
destTypeKind === UCTypeKind.Interface)
destTypeKind === UCTypeKind.Interface)
// Safety check to ensure that we are working with resolved types.
&& isClass(destType.getRef())
&& isClass(inputType.getRef())) {
Expand Down
7 changes: 6 additions & 1 deletion server/src/UC/documentASTWalker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ function createQualifiedType(ctx: UCGrammar.QualifiedIdentifierContext, kind?: U
leftType.setExpectedKind(UCSymbolKind.Package);
break;

case UCSymbolKind.Enum:
leftType.setExpectedKind(UCSymbolKind.Enum);
rightType.setExpectedKind(UCSymbolKind.EnumTag);
break;

default:
leftType.setExpectedKind(UCSymbolKind.Class);
break;
Expand Down Expand Up @@ -934,7 +939,7 @@ export class DocumentASTWalker extends AbstractParseTreeVisitor<any> implements

const qualifiedNode = arrayDimNode.qualifiedIdentifier();
if (qualifiedNode) {
property.arrayDimRef = qualifiedNode.accept(this);
property.arrayDimRef = createQualifiedType(qualifiedNode, UCSymbolKind.Enum);
property.arrayDimRange = property.arrayDimRef?.range;
return;
}
Expand Down
7 changes: 3 additions & 4 deletions server/src/UC/expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
ITypeSymbol,
IWithInnerSymbols,
Identifier,
IntrinsicClass,
IntrinsicNewConstructor,
IntrinsicObject,
IntrinsicRngLiteral,
Expand Down Expand Up @@ -58,7 +57,7 @@ import {
findOverloadedBinaryOperator,
findOverloadedPostOperator,
findOverloadedPreOperator,
findSuperStruct,
findSuperStructSymbol,
getContext,
getSymbolHash,
getSymbolOuterHash,
Expand All @@ -76,7 +75,7 @@ import {
import { UCDocument } from './document';
import { intersectsWith } from './helpers';
import { config, getConstSymbol, getEnumMember } from './indexer';
import { NAME_CLASS, NAME_OUTER, NAME_ROTATOR, NAME_SPAWN, NAME_STRUCT, NAME_VECTOR } from './names';
import { NAME_CLASS, NAME_OUTER, NAME_ROTATOR, NAME_STRUCT, NAME_VECTOR } from './names';
import { SymbolWalker } from './symbolWalker';

export interface IExpression extends INode, IWithInnerSymbols {
Expand Down Expand Up @@ -1089,7 +1088,7 @@ export class UCSuperExpression extends UCExpression {
// FIXME: UE2 doesn't verify inheritance, thus particular exploits are possible by calling a super function through an unrelated class,
// -- this let's programmers write data in different parts of the memory.
// -- Thus should we just be naive and match any type instead?
const symbol = findSuperStruct(context, this.structTypeRef.getName()) ?? tryFindClassSymbol(this.structTypeRef.getName());
const symbol = findSuperStructSymbol(context, this.structTypeRef.getName()) ?? tryFindClassSymbol(this.structTypeRef.getName());
if (isStruct(symbol)) {
this.structTypeRef.setRef(symbol, document);
this.superStruct = symbol;
Expand Down
8 changes: 8 additions & 0 deletions server/src/UC/test/enum/EnumDependencyClass.uc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class EnumDependencyClass extends Core.Object;

enum EEnumDependencyTest
{
EDT_None,
EDT_Other,
EDT_3
};
Loading

0 comments on commit b22a4cd

Please sign in to comment.