import { Type, BoundedArrayType, AtomicType, PointerType, ObjectPointerType, ArrayPointerType, ArrayElemType, Char, Int, CompleteObjectType, CompleteClassType, toHexadecimalString } from "../compilation/types"; import { Observable } from "../../util/observe"; import { assert, Mutable, asMutable } from "../../util/util"; import { Value, RawValueType } from "./Value"; import { Memory } from "./Memory"; import { RuntimeConstruct } from "../constructs/CPPConstruct"; import { CompiledClassDefinition } from "../constructs/declarations/class/ClassDefinition"; import { ParameterDefinition } from "../constructs/declarations/function/ParameterDefinition"; import { GlobalVariableDefinition, CompiledGlobalVariableDefinition } from "../constructs/declarations/variable/GlobalVariableDefinition"; import { LocalVariableDefinition } from "../constructs/declarations/variable/LocalVariableDefinition"; import { BoundReferenceEntity } from "../compilation/entities"; export interface ObjectDescription { name: string; message: string; } abstract class ObjectData { protected readonly object: CPPObject; protected readonly size: number; protected readonly memory: Memory; protected readonly address: number; public constructor(object: CPPObject, memory: Memory, address: number) { this.object = object; this.size = this.object.size; this.memory = memory; this.address = address; } // public abstract rawValue() : RawValueType; // public abstract setRawValue(newValue: RawValueType, write: boolean) : void; public abstract kill(rt?: RuntimeConstruct): void; public abstract zeroInitialize(): void; }; class AtomicObjectData extends ObjectData { public getValue(isValid: boolean) { return new Value(this.rawValue(), this.object.type, isValid); } public rawValue() { var bytes = this.memory.readBytes(this.address, this.size); return this.object.type.bytesToValue(bytes); } public setRawValue(newValue: RawValueType, write: boolean) { this.memory.writeBytes(this.address, this.object.type.valueToBytes(newValue)); } public zeroInitialize() { this.setRawValue(0, false); } public kill() { // no subobjects, do nothing } } class ArrayObjectData extends ObjectData> { // public static create(object: CPPObject>, memory: Memory, address: number) { // return new ArrayObjectData(object, memory, address); // } private readonly elemObjects: ArraySubobject[]; public constructor(object: CPPObject>, memory: Memory, address: number) { super(object, memory, address); let subAddr = this.address; this.elemObjects = []; for (let i = 0; i < this.object.type.numElems; ++i) { this.elemObjects.push(new ArraySubobject(this.object, i, memory, subAddr)); subAddr += this.object.type.elemType.size; } } public getArrayElemSubobjectByAddress(address: number) { let index = (address - this.address) / this.object.type.elemType.size; return this.getArrayElemSubobject(index); } public getArrayElemSubobject(index: number): ArraySubobject { if (0 <= index && index < this.elemObjects.length) { return this.elemObjects[index]; } else { let outOfBoundsObj = new ArraySubobject(this.object, index, this.memory, this.address + index * this.object.type.elemType.size); outOfBoundsObj.setValidity(false); return outOfBoundsObj; } } public getArrayElemSubobjects(): readonly ArraySubobject[] { return this.elemObjects; } public numArrayElemSubobjects(): number { return this.elemObjects.length; } public getValue() { return this.elemObjects.map((elemObj) => { return elemObj.getValue(); }); } public rawValue() { return this.elemObjects.map((elemObj) => { return elemObj.rawValue(); }); } // public setRawValue(newValue: RawValueType, write: boolean) { // for(var i = 0; i < (this.object.type).length; ++i){ // this.elemObjects[i].setValue(newValue[i], write); // } // } public zeroInitialize() { this.elemObjects.forEach(elemObj => elemObj.zeroInitialize()); } public kill(rt?: RuntimeConstruct) { this.elemObjects.forEach(elemObj => elemObj.kill(rt)); } } class ClassObjectData extends ObjectData { private readonly subobjects: readonly Subobject[]; private readonly baseSubobjects: readonly BaseSubobject[]; private readonly memberSubobjects: readonly MemberSubobject[]; private readonly memberObjectMap: {[index: string]: CPPObject | undefined} = {}; public constructor(object: CPPObject, memory: Memory, address: number) { super(object, memory, address); let subAddr = this.address; let classDef = this.object.type.classDefinition; assert(classDef?.isSuccessfullyCompiled(), "Cannot create an object at runtime for a class type that has not been defined and successfully compiled."); // this.baseSubobjects = classDef.baseSpecifiers.map((base) => { // let subObj = new BaseSubobject(this.object, base.baseEntity.type, memory, subAddr); // // let subObj = base.objectInstance(this.object, memory, subAddr); // subAddr += subObj.size; // return subObj; // }); this.baseSubobjects = []; if (classDef.baseType) { let baseObj = new BaseSubobject(this.object, classDef.baseType, memory, subAddr); asMutable(this.baseSubobjects).push(baseObj); subAddr += baseObj.size; } this.memberSubobjects = classDef.memberObjectEntities.map((mem) => { let subObj = new MemberSubobject(this.object, mem.type, mem.name, memory, subAddr); // let subObj = mem.objectInstance(this.object, memory, subAddr); subAddr += subObj.size; this.memberObjectMap[mem.name] = subObj; return subObj; }); this.subobjects = []; this.subobjects = this.subobjects.concat(this.baseSubobjects).concat(this.memberSubobjects); } public getMemberObject(name: string) : CPPObject | undefined { return this.memberObjectMap[name] ?? this.baseSubobjects[0]?.getMemberObject(name); } public bindMemberReference(name: string, obj: CPPObject) { this.memberObjectMap[name] = obj; } public getBaseSubobject() : BaseSubobject | undefined { return this.baseSubobjects[0]; } // TODO: Could remove? This isn't currently used and I don't think it's useful for anything // public getSubobjectByAddress(address: number) { // for(var i = 0; i < this.subobjects.length; ++i) { // var subObj = this.subobjects[i]; // if (subObj.address === address){ // return subObj; // } // } // } // public rawValue() { // return this.subobjects.map((subObj) => { return subObj.rawValue(); }); // } // public setRawValue(newValue: RawValueType, write: boolean) { // for(var i = 0; i < this.subobjects.length; ++i) { // this.subobjects[i].setValue(newValue[i], write); // } // } public getValue(): never { throw new Error("Not implemented"); } public rawValue(): never { throw new Error("Not implemented"); } public zeroInitialize() { this.subobjects.forEach(subobj => subobj.zeroInitialize()); } public kill(rt?: RuntimeConstruct) { this.subobjects.forEach(subobj => subobj.kill(rt)); } } // interface CPPObjectDescriptor { // describe(obj: CPPObject) : Description; // } // const OBJECT_DESCRIPTORS = { // DEFAULT : { // describe: (obj: CPPObject) => { // return {message: "an object at 0x" + obj.address}; // } // }, // AUTO : { // describe: (obj: CPPObject) => { // return {message: "the heap object at 0x" + obj.address}; // } // }, // DYNAMIC : { // describe: (obj: CPPObject) => { // return {message: "the heap object at 0x" + obj.address}; // } // }, // INVALID : { // describe: (obj: CPPObject) => { // return {message: "an invalid object at 0x" + obj.address}; // } // }, // THIS : { // describe: (obj: CPPObject) => { // return {name: "this", message: "the this pointer"}; // } // }, // STRING_LITERAL : { // describe: (obj: CPPObject) => { // return {message: "string literal at 0x" + obj.address}; // } // } // } // class EntityObjectDescriptor { // private entity: CPPEntity; // constructor(entity: CPPEntity) { // this.entity = entity; // } // describe(obj: CPPObject) { // return this.entity.describe(); // } // } export type CPPObjectStorageKind = "automatic" | "dynamic" | "static" | "temporary" | "subobject" | "invalid"; type ObjectValueRepresentation = T extends AtomicType ? Value : T extends BoundedArrayType ? ObjectValueRepresentation[] : T extends CompleteClassType ? { [index: string]: ObjectRawValueRepresentation } : never; type ObjectRawValueRepresentation = T extends AtomicType ? RawValueType : T extends BoundedArrayType ? ObjectRawValueRepresentation[] : T extends CompleteClassType ? { [index: string]: ObjectRawValueRepresentation } : unknown; // TODO: it may be more elegant to split into 3 derived types of CPPObject for arrays, classes, and // atomic objects and use a public factory function to create the appropriate instance based on the // template parameter. (Rather than the current awkward composition and conditional method strategy) export abstract class CPPObject { private static _nextObjectId = 0; public readonly observable = new Observable(this); /** * Objects that result from a named declaration will have a name. For others * (e.g. dynamically allocated objects, temporary objects), this will be undefined. */ public readonly name?: string; public readonly type: T; public readonly size: number; public readonly address: number; public readonly objectId; public abstract readonly storageKind: CPPObjectStorageKind; private readonly data: any; public readonly isAlive: boolean; public readonly deallocatedBy?: RuntimeConstruct; private _isValid: boolean; private readonly auxiliaryData : {[index: string]: any} = {}; public constructor(type: T, memory: Memory, address: number) { this.type = type; this.size = type.size; this.objectId = CPPObject._nextObjectId++; assert(this.size != 0, "Size cannot be 0."); // SCARY if (this.type.isBoundedArrayType()) { // this.isArray = true; this.data = new ArrayObjectData(this, memory, address); } else if (this.type.isCompleteClassType()) { this.data = new ClassObjectData(this, memory, address); } else { this.data = new AtomicObjectData(this, memory, address); } this.address = address; // Object is not alive until it is initialized this.isAlive = false; // Validity is determined by the data this object currently holds this._isValid = false; } // Only allowed if receiver matches CPPObject> public getArrayElemSubobject(this: CPPObject, index: number): ArraySubobject { return this.data.getArrayElemSubobject(index); } // Only allowed if receiver matches CPPObject> public getArrayElemSubobjects(this: CPPObject): readonly ArraySubobject[] { return this.data.getArrayElemSubobjects(); } // Only allowed if receiver matches CPPObject> public numArrayElemSubobjects(this: CPPObject): number { return this.data.numArrayElemSubobjects(); } // Only allowed if receiver matches CPPObject> public getArrayElemSubobjectByAddress(this: CPPObject, address: number): ArraySubobject { return this.data.getArrayElemSubobjectByAddress(address); } /** * Only allowed if receiver matches CPPObject * Note that this returns CPPObject rather than MemberSubobject because * a reference member may refer to an external object that is not a subobject * @param this * @param name */ public getMemberObject(this: CPPObject, name: string) { return (>this.data).getMemberObject(name); } // Only allowed if receiver matches CPPObject public bindMemberReference(this: CPPObject, name: string, obj: CPPObject) { return (>this.data).bindMemberReference(name, obj); } // Only allowed if receiver matches CPPObject public getBaseSubobject(this: CPPObject) { return (>this.data).getBaseSubobject(); } public subobjectValueWritten() { this.observable.send("valueWritten"); } public toString() { return "@" + this.address; } public beginLifetime() { assert(!this.isAlive); asMutable(this).isAlive = true; this.observable.send("lifetimeBegan", this); } public kill(rt?: RuntimeConstruct) { // kill subobjects this.data.kill(rt); (>this).isAlive = false; this._isValid = false; if (rt) { (>this).deallocatedBy = rt; } this.observable.send("deallocated"); } public getPointerTo(): Value> { return new Value(this.address, new ObjectPointerType(this)); } // Only allowed if receiver matches CPPObject> public decayToPointer(this: CPPObject) : Value> { return this.getArrayElemSubobject(0).getPointerTo(); } public isBaseSubobject() : this is BaseSubobject { return false; } public getValue(read: boolean = false): ObjectValueRepresentation { let val = this.data.getValue(this._isValid); if (read) { this.observable.send("valueRead", val); } return val; } public rawValue(): ObjectRawValueRepresentation { return this.data.rawValue(); } public readValue(): ObjectValueRepresentation { return this.getValue(true); } public setValue(this: CPPObject, newValue: Value, write: boolean = false) { this._isValid = newValue.isValid; // Accept new RTTI // However, we need to retain our own CV qualifiers asMutable(this).type = newValue.type.cvQualified(this.type.isConst, this.type.isVolatile); this.data.setRawValue(newValue.rawValue, write); if (write) { this.observable.send("valueWritten", newValue); } this._onValueSet(write); } public _onValueSet(write: boolean) { } public writeValue(this: CPPObject, newValue: Value) { this.setValue(newValue, true); } /** * Begins this object's lifetime and initializes its value. * May only be called on objects of atomic type. * @param this * @param newValue */ public initializeValue(this: CPPObject, newValue: Value) { this.beginLifetime(); this.writeValue(newValue); } public zeroInitialize() { this.data.zeroInitialize(); this.setValidity(true); } public isValueValid(this: CPPObject): boolean { return this._isValid && this.type.isValueValid(this.rawValue()); } // TODO: figure out whether this old code is worth keeping // originally, these functions were used to notify an object when somebody else // messed with (i.e. read/wrote bytes that were part of the object). // byteRead: function(addr){ // if (this.isArray){ // // If array, find the subobject containing the byte // this.elemObjects[(addr - this.address) / this.nonRefType.elemType.size].byteRead(addr); // } // else if (this.isClass){ // var ad = this.address; // for(var i = 0; i < this.subobjects.length; ++i) { // var mem = this.subobjects[i]; // if(ad = ad + mem.type.size > addr){ // ad.byteRead(addr); // break; // } // } // } // else{ // this.send("byteRead", {addr: addr}); // } // }, // bytesRead: function(addr, length){ // if (this.isArray) { // var beginIndex = Math.max(0, Math.floor(( addr - this.address ) / this.nonRefType.elemType.size)); // var endIndex = Math.min( // beginIndex + Math.ceil(length / this.nonRefType.elemType.size), // this.nonRefType.length); // for (var i = beginIndex; i < endIndex; ++i) { // this.elemObjects[i].bytesRead(addr, length); // } // } // else if (this.isClass){ // for(var i = 0; i < this.subobjects.length; ++i) { // var mem = this.subobjects[i]; // if(addr < mem.address + mem.type.size && mem.address < addr + length){ // check for overlap // mem.bytesRead(addr, length); // } // else if (mem.address > addr +length){ // // break if we are now in members past affected bytes // break; // } // } // } // else{ // this.send("bytesRead", {addr: addr, length: length}); // } // }, // byteSet: function(addr, value){ // if (this.isArray){ // // If array, find the subobject containing the byte // this.elemObjects[(addr - this.address) / this.nonRefType.elemType.size].byteSet(addr, value); // } // else if (this.isClass){ // var ad = this.address; // for(var i = 0; i < this.subobjects.length; ++i) { // var mem = this.subobjects[i]; // if(ad = ad + mem.type.size > addr){ // mem.byteSet(addr, value); // break; // } // } // } // else{ // this.send("byteSet", {addr: addr, value: value}); // } // }, // bytesSet: function(addr, values){ // var length = values.length; // if (this.isArray) { // var beginIndex = Math.max(0, Math.floor(( addr - this.address ) / this.nonRefType.elemType.size)); // var endIndex = Math.min( // beginIndex + Math.ceil(length / this.nonRefType.elemType.size), // this.nonRefType.length); // for (var i = beginIndex; i < endIndex; ++i) { // this.elemObjects[i].bytesSet(addr, values); // } // } // else if (this.isClass){ // for(var i = 0; i < this.subobjects.length; ++i) { // var mem = this.subobjects[i]; // if(addr < mem.address + mem.type.size && mem.address < addr + length){ // check for overlap // mem.bytesSet(addr, values); // } // else if (mem.address > addr +length){ // // break if we are now in members past affected bytes // break; // } // } // } // else{ // this.send("bytesSet", {addr: addr, values: values}); // } // }, // byteWritten: function(addr, value){ // if (this.isArray){ // // If array, find the subobject containing the byte // this.elemObjects[(addr - this.address) / this.nonRefType.elemType.size].byteWritten(addr, value); // } // else if (this.isClass){ // var ad = this.address; // for(var i = 0; i < this.subobjects.length; ++i) { // var mem = this.subobjects[i]; // if(ad = ad + mem.type.size > addr){ // mem.byteWritten(addr, value); // break; // } // } // } // else{ // this.send("byteWritten", {addr: addr, value: value}); // } // }, // bytesWritten: function(addr, values){ // var length = values.length; // if (this.isArray) { // var beginIndex = Math.max(0, Math.floor(( addr - this.address ) / this.nonRefType.elemType.size)); // var endIndex = Math.min( // beginIndex + Math.ceil(length / this.nonRefType.elemType.size), // this.nonRefType.length); // for (var i = beginIndex; i < endIndex; ++i) { // this.elemObjects[i].bytesWritten(addr, values); // } // } // else if (this.isClass){ // for(var i = 0; i < this.subobjects.length; ++i) { // var mem = this.subobjects[i]; // if(addr < mem.address + mem.type.size && mem.address < addr + length){ // check for overlap // mem.bytesWritten(addr, values); // } // else if (mem.address > addr +length){ // // break if we are now in members past affected bytes // break; // } // } // } // else{ // this.send("bytesWritten", {addr: addr, values: values}); // } // }, public callReceived() { this.observable.send("callReceived", this); } public callEnded() { this.observable.send("callEnded", this); } public setValidity(valid: boolean) { this._isValid = valid; this.observable.send("validitySet", valid); } public abstract isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is CPPObject; public hasStorage(storageKind: "dynamic") : this is DynamicObject; public hasStorage(storageKind: "automatic") : this is AutoObject; public hasStorage(storageKind: "subobject") : this is Subobject; public hasStorage(storageKind: "temporary") : this is TemporaryObject; public hasStorage(storageKind: "invalid") : this is InvalidObject; public hasStorage(storageKind: CPPObjectStorageKind) { return this.storageKind === storageKind; } /** * Notify this object that a reference has been bound to it */ public onReferenceBound(entity: BoundReferenceEntity) { this.observable.send("referenceBoundToMe", entity); } /** * Notify this object that a reference has been unbound from it * (e.g. that reference went out of scope) */ public onReferenceUnbound(entity: BoundReferenceEntity) { this.observable.send("referenceUnbound", entity); } public abstract describe(): ObjectDescription; public setAuxiliaryData(key: string, data: any) { this.auxiliaryData[key] = data; } public getAuxiliaryData(key: string) { return this.auxiliaryData[key]; } }; // TODO: Kept some of this in a comment in case it's helpful when reintroducing leak checking // export class DynamicObject extends CPPObject { // private hasBeenLeaked: boolean = false; // public constructor(type: ObjectType, memory: Memory, address: number) { // super(type, memory, address); // } // public toString() { // return "Heap object at " + this.address + " (" + this.type + ")"; // } // public leaked(sim: Simulation) { // if (!this.hasBeenLeaked){ // this.hasBeenLeaked = true; // sim.memoryLeaked("Oh no! Some memory just got lost. It's highlighted in red in the memory display.") // this.observable.send("leaked"); // } // } // // TODO: Why does this exist? How does something become unleaked?? // public unleaked(sim: Simulation) { // this.observable.send("unleaked"); // } // public describe() { // return {message: "the heap object at 0x" + this.address}; // } // } // TODO: remove this? // export var EvaluationResultRuntimeEntity = CPPObject.extend({ // _name: "EvaluationResultRuntimeEntity", // storage: "automatic", // init: function(type, inst){ // this.initParent(null, type); // this.inst = inst; // }, // instanceString : function(){ // return this.name + " (" + this.type + ")"; // }, // runtimeLookup : function(sim: Simulation, rtConstruct: RuntimeConstruct) { // return this.inst.evalResult.runtimeLookup(sim, inst); // } // }); // public static createAutoObject(entity: AutoEntity, memory: Memory, address: number) { // return new CPPObject(entity.type, memory, address, new EntityObjectDescriptor(entity)); // } export class AutoObject extends CPPObject { public readonly storageKind = "automatic"; public readonly name: string; public readonly def: LocalVariableDefinition | ParameterDefinition public constructor(def: LocalVariableDefinition | ParameterDefinition, type: T, memory: Memory, address: number) { super(type, memory, address); this.def = def; this.name = def.name; } public describe(): ObjectDescription { return this.def.declaredEntity.describe(); } public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is AutoObject { return predicate(this.type); } } export class MainReturnObject extends CPPObject { public readonly storageKind = "static"; public constructor(memory: Memory) { super(Int.INT, memory, 0); // HACK: put it at address 0. probably won't cause any issues since it's not allocated } public isTyped(predicate: (t:Int) => t is NarrowedT) : this is MainReturnObject; public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is never; public isTyped(predicate: (t:Int) => t is NarrowedT) : this is MainReturnObject { return predicate(this.type); } public describe(): ObjectDescription { return { name: "[main() return]", message: "The value returned from main." } } } export class StaticObject extends CPPObject { public readonly storageKind = "static"; public readonly name: string; public constructor(public readonly def: CompiledGlobalVariableDefinition, type: T, memory: Memory, address: number) { super(type, memory, address); this.name = def.name; } public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is StaticObject { return predicate(this.type); } public describe(): ObjectDescription { return this.def.declaredEntity.describe(); } } export class DynamicObject extends CPPObject { public readonly storageKind = "dynamic"; public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is DynamicObject { return predicate(this.type); } public describe(): ObjectDescription { return { name: `[dynamic @${this.address}]`, message: `the heap object at ${toHexadecimalString(this.address)}` }; } } export class InvalidObject extends CPPObject { public readonly storageKind = "invalid"; public constructor(type: T, memory: Memory, address: number) { super(type, memory, address); this.setValidity(false); this.kill(); } public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is InvalidObject { return predicate(this.type); } public describe(): ObjectDescription { return { name: `[invalid @${this.address}]`, message: `an invalid object at ${toHexadecimalString(this.address)}` }; } } // export class ThisObject extends CPPObject { // public isTyped(predicate: (t:CompleteClassType) => t is NarrowedT) : this is ThisObject; // public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is never; // public isTyped(predicate: (t:CompleteClassType) => t is NarrowedT) : this is ThisObject { // return predicate(this.type); // } // public describe(): ObjectDescription { // return { name: "this", message: "the this pointer" }; // } // } export class StringLiteralObject extends CPPObject> { public readonly storageKind = "static"; public constructor(type: BoundedArrayType, memory: Memory, address: number) { super(type, memory, address); } public isTyped>(predicate: (t:BoundedArrayType) => t is NarrowedT) : this is StringLiteralObject; public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is never; public isTyped>(predicate: (t:BoundedArrayType) => t is NarrowedT) : this is StringLiteralObject { return predicate(this.type); } public describe(): ObjectDescription { return { name: `[string literal @${this.address}]`, message: `string literal at ${toHexadecimalString(this.address)}` } } } abstract class Subobject extends CPPObject { public readonly storageKind = "subobject"; public readonly containingObject: CPPObject; public constructor(containingObject: CPPObject, type: T, memory: Memory, address: number) { super(type, memory, address); this.containingObject = containingObject; } public _onValueSet(write: boolean) { if (write) { this.containingObject.subobjectValueWritten(); } } } export class ArraySubobject extends Subobject { public readonly containingObject!: CPPObject>; // Handled by parent (TODO: is this a good idea?) lol no i don't think so public readonly index: number; public constructor(arrObj: CPPObject>, index: number, memory: Memory, address: number) { super(arrObj, arrObj.type.elemType, memory, address); this.index = index; } public getPointerTo() { return new Value(this.address, new ArrayPointerType(this.containingObject)); } public isTyped(predicate: (t:ArrayElemType) => t is NarrowedT) : this is ArraySubobject; public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is never; public isTyped(predicate: (t:ArrayElemType) => t is NarrowedT) : this is ArraySubobject { return predicate(this.type); } describe() { var arrDesc = this.containingObject.describe(); return { name: arrDesc.name + "[" + this.index + "]", message: "element " + this.index + " of " + arrDesc.message, }; } } export class BaseSubobject extends Subobject { public readonly containingObject!: CPPObject; // Handled by parent (TODO: is this a good idea?) public constructor(containingObject: CPPObject, type: CompleteClassType, memory: Memory, address: number) { super(containingObject, type, memory, address); } public isTyped(predicate: (t:CompleteClassType) => t is NarrowedT) : this is BaseSubobject; public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is never; public isTyped(predicate: (t:CompleteClassType) => t is NarrowedT) : this is BaseSubobject { return predicate(this.type); } public override isBaseSubobject() : this is BaseSubobject { return true; } public describe(): ObjectDescription { let contDesc = this.containingObject.describe(); return { name: `[${this.type.className} base of ${contDesc.name}]`, message: "the " + this.type.className + " base of " + contDesc.message }; } } export class MemberSubobject extends Subobject { public readonly containingObject!: CPPObject; // Handled by parent (TODO: is this a good idea?) public readonly name: string; public constructor(containingObject: CPPObject, type: T, name: string, memory: Memory, address: number) { super(containingObject, type, memory, address); this.name = name; } public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is MemberSubobject { return predicate(this.type); } public describe() { var parent = this.containingObject; let parentDesc = parent.describe(); return { name: parentDesc.name + "." + this.name, message: "the member " + this.name + " of " + parentDesc.message } } } // export type TemporaryObjectType = T extends ObjectType ? TemporaryObject : never; export class TemporaryObject extends CPPObject { public readonly storageKind = "temporary"; private description: string; // public static create(type: T, memory: Memory, address: number, name?: string) : T extends ObjectType ? TemporaryObject : never { // return new TemporaryObject(type, memory, address, name); // } public constructor(type: T, memory: Memory, address: number, description: string) { super(type, memory, address); this.description = description; // this.entityId = tempObjEntity.entityId; } public isTyped(predicate: (t:CompleteObjectType) => t is NarrowedT) : this is TemporaryObject { return predicate(this.type); } public describe(): ObjectDescription { return { name: this.description, message: "the temporary object " + this.name }; } }