Skip to content

Conversation

@mfedderly
Copy link
Collaborator

Cherry-picking some of the work from #2970 so that we can merge it today instead of waiting for a major release.

Specifically we can further narrow the return types of geometry and geometryCollection based on what the user sends in the arguments.

return multiLineString(coordinates).geometry as any;
case "MultiPolygon":
return multiPolygon(coordinates).geometry;
return multiPolygon(coordinates).geometry as any;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This as any casts are a little unfortunate. The case statements don't actually narrow our understanding of T, and so we need to reaffirm that we know what we're doing here.

microsoft/TypeScript#33014

MultiPoint: MultiPoint;
MultiLineString: MultiLineString;
MultiPolygon: MultiPolygon;
}[T] {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had considered making this a named interface and mapping against that instead (GeometryTypeNameToType), and referencing that instead of as any below, but I opted to not increase the public API for a mapping we only used in a single place.

| "MultiPolygon",
>(
type: T,
coordinates: any[],
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a simliar fashion, we could narrow this any[] based on T, but that would be a breaking change, we can file it against the 8.0 milestone. I suppose you could also just make the case that we should do this in 7.x, because it is explicitly a bug in the caller if they aren't passing in the correctly shaped coordinates. My only concern with that is some amount of weirdness with number[] vs [number, number] issues, which might crop up depending on how consumers wrote their own types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The [number, number] thing is going to haunt us forever 😅

return point(coordinates).geometry as any;
case "LineString":
return lineString(coordinates).geometry;
return lineString(coordinates).geometry satisfies LineString as any;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need the satisfies as well? I wasn't sure if LineString is different from the others.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 this was an intermediate state but I decided that the satisfies step didn't really add anything and I removed it (or at least I thought I removed it).

@smallsaucepan
Copy link
Member

Looks good @mfedderly. While trying out a couple of things, I found the below a shade more readable. Produces the same result though, so only mentioning it in case you agree on the look of it. The helper doesn't need to be exported so shouldn't affect the public interface.

type GeometryByType<T extends Geometry["type"]> = Extract<
  Geometry,
  { type: T }
>;

export function geometry<
  T extends Exclude<Geometry, GeometryCollection>["type"],
>(
  type: T,
  coordinates: any[],
  _options: Record<string, never> = {}
): GeometryByType<T> {
  switch (type) {
    case "Point":
      return point(coordinates).geometry as GeometryByType<T>;
    case "LineString":
      return lineString(coordinates).geometry as GeometryByType<T>;
    case "Polygon":
      return polygon(coordinates).geometry as GeometryByType<T>;
    case "MultiPoint":
      return multiPoint(coordinates).geometry as GeometryByType<T>;
    case "MultiLineString":
      return multiLineString(coordinates).geometry as GeometryByType<T>;
    case "MultiPolygon":
      return multiPolygon(coordinates).geometry as GeometryByType<T>;
    default:
      throw new Error(type + " is invalid");
  }
}

@mfedderly
Copy link
Collaborator Author

The helper doesn't need to be exported so shouldn't affect the public interface.

I think this is actually untrue. Microsoft built a tool called api-extractor with a rule for exactly this circumstance. The docs page explains some of the pitfalls of not exporting referenced types.

https://api-extractor.com/pages/messages/ae-forgotten-export/

@mfedderly mfedderly merged commit 9f01ae5 into master Dec 16, 2025
4 checks passed
@mfedderly mfedderly deleted the mf/helpers-type-inferring branch December 16, 2025 13:24
@smallsaucepan
Copy link
Member

the pitfalls of not exporting referenced types

Which largely wouldn't apply in our case i.e. an internal type inference helper.

Even taking API extractor warnings as gospel, it doesn't have to be defined as a type - inlining Extract<Geometry, { type: T }> produces the same result. It's as readable and would at least avoid those casts to any, which I think is worth doing where we can 🤷‍♂️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants