16

I am trying to create the following interface in typescript:

type MoveSpeed = "min" | "road" | "full";

interface Interval {
  min?: number,
  max?: number
}

interface CreepPlan {
  [partName: string] : Interval;
  move?:    MoveSpeed;
}

However, this syntax is invalid. The compiler says Property 'move' of type '"min" | "road" | "full" | undefined' is not assignable to string index type 'Interval'..

I am using the compiler option strictNullChecks:true, which is why undefined is included implicitly for the move? key.

However, this syntax is valid:

type MoveSpeed = "min" | "road" | "full";

interface Interval {
  min?: number,
  max?: number
}

interface CreepPlan {
  [partName: string] : Interval;
  move:    MoveSpeed;            // 'move' is required
}

I want to express the idea "CreepPlan consists of string:Interval pairs, except the optional 'move' key which is a string:MoveSpeed pair." Is it possible to express this in typescript?

2
  • 1
    I think the problem might be where (and how) you're using that code, not the code itself, since your example compiles without error Commented Oct 6, 2016 at 19:30
  • @ssube Apologies, I am using strictNullChecks set to true. I will clarify. Commented Oct 6, 2016 at 19:43

2 Answers 2

16

You stated that all properties on CreepPlan have Interval-type values when you wrote:

interface CreepPlan {
  [partName: string] : Interval;
}

i.e., any and every string-index-accessible property of CreepPlan will be an Interval. This applies to move as well, since foo['move'] is the same as foo.move.

However, looking at Interval, there's no required structure to it:

interface Interval {
  min?: number,
  max?: number
}

There are no required fields in that contract, but it does require an object. It will happily accept an empty object, but it requires some object that could have a min and max properties.

Your MoveSpeed type, on the other hand, allows undefined:

Property 'move' of type '"min" | "road" | "full" | undefined' is not assignable to string index type 'Interval'.

Thus, Interval must be an object with no required properties (which string can easily meet) but does not allow undefined, which MoveSpeed does allow.

Your types are disjoint, but it's not obvious until you resolve them out once or twice.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your answer. This was also my intuition, but what I don't understand is why the second example in my question (with move as a required member) is valid. Perhaps some quirk of the typescript compiler.
@Oliver I think I know. Editing it in, just a sec.
13

As described in the typescript docs if you use indexable properties then all the return type of all the other properties should be assignable to the return type of the index return type. That's because foo['bar'] and foo.bar are the same.

So why does this work?

interface CreepPlan {
    [partName: string]: Interval;
    move: MoveSpeed;
}

Because any MoveSpeed is a valid Interval value (it is an interval with undefined both min/max.

Why doesn't this work?

interface CreepPlan {
     [partName: string]: Interval;
     move?: MoveSpeed;
 }

move? has type MoveSpeed plus the undefined type. If is has the undefined value then it is not assignable to Interval so the compiler correctly shows error.

How can you fix this? Simply make the indexer have undefined as a valid value:

interface CreepPlan {
    [partName: string]: Interval | undefined;
    move?: MoveSpeed;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.