bufbuild / protobuf-es

Protocol Buffers for ECMAScript. The only JavaScript Protobuf library that is fully-compliant with Protobuf conformance tests.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Generated types allow assigning wrong message type if it is a superset of the target type

LinearSpoon opened this issue · comments

There is no compile error in const a: ProtoA = new ProtoB() if the fields in ProtoA are a subset of the fields in ProtoB.

example.proto

syntax = "proto3";

message Foo {
    int32 foo = 1;
}

message NotFoo {
    int32 foo = 2;  // Different tag number
}

// Foo with an extra field
message FooAndBar {
    int32 foo = 1;
    int32 bar = 2;  // Extra field
}

example.ts

import { Foo, FooAndBar, NotFoo } from "./example_pb.js";

// Unsafe - No compile time error
const a0: Foo = new FooAndBar();
const a1: Foo = new NotFoo();
const a2: NotFoo = new Foo();
const a3: NotFoo = new FooAndBar();

// Good - compile time error
const a4: FooAndBar = new Foo();  // Property 'bar' is missing in type 'Foo' but required in type 'FooAndBar'.
const a5: FooAndBar = new NotFoo();  // Property 'bar' is missing in type 'NotFoo' but required in type 'FooAndBar'.

// This also affects function parameters
function example(foo: Foo) { }
example(new NotFoo());  // Unsafe - No error

An example fix would be adding a dummy private variable to to every generated proto. TypeScript doesn't output any code when compiling this, so there should be no runtime implications (Playground Link).

Relevant TypeScript docs:

When an instance of a class is checked for compatibility, if the target type contains a private member, then the source type must also contain a private member that originated from the same class.

export class Foo extends Message<Foo> {
  private unused: any;

  // Existing generated code...
}

// Good - Compile time error
// Type 'FooAndBar' is not assignable to type 'Foo'.
//   Types have separate declarations of a private property 'unused'.
const a0: Foo = new FooAndBar();