screepers/Screeps-Typescript-Declarations

Allow typing of default memory objects

seancl opened this issue · 5 comments

It seems that currently, you can't apply an interface to members of the built-in Memory objects (such as Memory.creeps) because they're defined in index.d.ts as any. I'd like to propose making a dummy interface for each of the four built-in Memory objects so that a player can then extend those interfaces in their own definition files.

For example, say I make my own interface for a room's memory:

interface MemRoom {
  name: string;
  type: string;
  threat: boolean;
}

If I try to apply it to Memory as follows:

interface Memory {
  rooms: {
    [name: string]: MemRoom;
  };
}

Then with the current typings I will get this error:

error TS2403: Subsequent variable declarations must have the same type.  Variable 'rooms' must be of type '{ [name: string]: any; }', but here has type '{ [name: string]: MemRoom; }'.

If however, there were already a "dummy" interface in the typings called MemRoom:

interface Memory {
    [name: string]: any;
    ...
    rooms: {
        [name: string]: MemRoom;
    };
    ...
}
interface MemRoom {
  [name: string]: any;
}
declare class Room {
  ...
    /**
     * A shorthand to Memory.rooms[room.name]. You can use it for quick access the room’s specific memory data object.
     */
    memory: MemRoom;
  ...
}

Then I could extend MemRoom to give specific types to any variables I want (as I tried to do above). This could be done for all four default Memory objects, and would allow users to type things that they want to store in memory - a huge advantage, I think, since Memory is the biggest source of errors for me these days!

If there is already a way to accomplish this that doesn't require modifying the current screeps declarations, please let me know as I'd be very curious!

Looks viable to me, considering any other member definitions could be easily made to fit the [string]: any hashing strategy. As long as you can show that an optional property of a constrained type still compiles (e.g. testProperty?: number) then I'd say this is ready to go. The only backwards incompatibility would arrive from people using dot accessors for undeclared properties, at which point they'd just have to declare their own extensions to memory and everything would work as expected.

Thanks for the input @Dessix. I'm still a little concerned (as mentioned on the PR #61 ) that creating an interface like this will prevent users from being able to save a non-object in a room's memory. This is because the interface is always going to be interpreted as an object.

Although I don't think many people really use it this way, it is in theory perfectly legal to do:

Game.rooms[roomname].memory = 5;

... which TypeScript would reject using this proposal, since there's no way to make the interface MemRoom be a straight number. At least, not that I know of.

It's something that could be done by an alternate idea of a complete lack of definition, allowing the user to add their own interface by a name we utilize, or (maybe?) with an empty definition of an interface. Generally, however, everyone I've seen who has said they were doing that has changed away from it due to the inability to patch additional properties on at runtime during unusual circumstances. I think we're fine to do this as long as it allows basic type safety over memory (which we currently do not have), even if it may not be the optimal long-term solution.

I thought of the same idea - leaving an undeclared interface in - but I think that's probably a poor design choice. It would also cause compiler errors on the declarations repo itself.

I think you may be right, however, that type safety in memory is worth the downside of not being able to store a bare value in room/spawn/creep/flag memory. If others agree, then the current PR ought to suffice to do just that.

I put a PR for this issue here #107

The use case of storing a bare value is such an outlier, I don't think it's worth considering.

And, in the situation that a consumer adamantly wanted to store a single value, they could trivially update their code to conform to the new typing.

Otherwise, you are essentially weighing the cost of:

  • it takes an extra step to store just a literal in memory

vs.

  • Memory is untypable

and then voting for the first case