Skip to content

model: Use enum for Message.flags[] #298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 7, 2023

Conversation

chrisbobbe
Copy link
Collaborator

@chrisbobbe chrisbobbe commented Sep 6, 2023

This contains commits cherry-picked from other PRs:

From my f59da2a / #286:

test [nfc]: When deepToJson returns Map, don't lose its string-keyed type

From @sirpengi's 00bc224 / #287 (thanks @sirpengi!):

model: Add DmNarrow.withUser helper

Related: #253

sirpengi and others added 2 commits September 6, 2023 15:16
…type

In practice the returned map has always had string keys, but its
`runtimeType` has apparently been _Map<dynamic, Object?>. Make it be
Map<String, dynamic> instead.

If you have an instance of one of our data classes, and one of its
fields is represented in the `toJson` form as a string-keyed Map,
then now the instance can pass through `deepToJson` and back through
the class's `fromJson` factory without having a runtime type-casting
error in `fromJson`.
@chrisbobbe
Copy link
Collaborator Author

(Updated PR description to acknowledge commits cherry-picked from other PRs.)

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Thanks! Glad to have this TODO resolved. Comments below.

Comment on lines 284 to 285
static Object? _flagsToJson(Set<Flag> value) {
return value.map((flag) => flag.toJson()).toList();
Copy link
Member

Choose a reason for hiding this comment

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

The toJson call isn't needed here, right? In that our convention generally is that toJson isn't recursive, and the recursion is left up to jsonEncode or to our tests' deepToJson.

(And then at that point this method no longer does anything and can be dropped.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Huh, interesting, right. I think that means that the whole _flagsToJson just isn't needed.

@@ -305,6 +315,32 @@ sealed class Message {
Map<String, dynamic> toJson();
}

/// As in [Message.flags].
@JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true)
enum Flag {
Copy link
Member

Choose a reason for hiding this comment

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

How about MessageFlag? I feel like Flag, as a public name visible across our codebase, is nonspecific enough that it sounds like it could be a lot of things depending on the context.

@@ -341,7 +341,7 @@ class UpdateMessageEvent extends Event {
final bool? renderingOnly; // TODO(server-5)
final int messageId;
final List<int> messageIds;
final List<String> flags; // TODO enum
final Set<Flag> flags;
Copy link
Member

Choose a reason for hiding this comment

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

model: Make `flags` on Message and UpdateMessageEvent a Set instead of List

This will enable more efficient checking of whether a message has a
certain flag.

I think this is actually a case where a List is likely to be more efficient than a Set.

The reason is that these lists will always be short, and almost always be very short. There are 7 different flags now; the list might grow occasionally but will probably never be much more than 10; so the list will never be longer than that. And most of the flags will apply to a small fraction of messages; probably over 90% of the lists of flags will be either just [Flag.read], or empty.

When the list/set is small, checking membership by just looping through wins for speed because of its simplicity — there's just so little work to do. In particular when the length is 0 or 1, the loop is basically impossible to beat.

Copy link
Member

Choose a reason for hiding this comment

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

If we wanted to really optimize this — for speed and also for space — the way to do it would be with a single int that contains flag bits. That might actually turn out to be worthwhile, given that messages are something we can have a lot of. But we can leave it out at this stage.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Interesting. Sure, we can keep it as a List.

It would have been nice to be able to annotate [MessageFlag.unknown]
as the "unknown" value inline, right there in the enum definition.

If the enum described a field directly on the Message class, we
could at least have annotated that field with
  @jsonkey(unknownEnumValue: MessageFlag.unknown)
and that would be nice and compact. But the field is a
List<MessageFlag>, not a MessageFlag, so we have to verbosely write
out `fromJson` for it, ah, well.
@chrisbobbe chrisbobbe changed the title model: For message flags, use a set of enum values, not a list of strings model: Use enum for Message.flags[] Sep 6, 2023
@chrisbobbe
Copy link
Collaborator Author

Thanks for the review! Revision pushed. I've updated the PR title so it doesn't say we're using a Set anymore.

@gnprice
Copy link
Member

gnprice commented Sep 7, 2023

Thanks! Looks good; merging.

@gnprice gnprice merged commit 9829f60 into zulip:main Sep 7, 2023
@chrisbobbe chrisbobbe deleted the pr-flags-set-enum branch September 7, 2023 00:24
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