-
Notifications
You must be signed in to change notification settings - Fork 236
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
Distinguish a TopicName type from plain String #1266
Open
gnprice
wants to merge
26
commits into
zulip:main
Choose a base branch
from
gnprice:pr-topic-name
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The topic/subject field on DMs carries no information in the current API, and we already never actually look at it, as demonstrated by the lack of changes in the rest of the tree in this commit. Clean up the type definitions in the API a bit by making that explicit. This is NFC except in the case where a server were to not send this field at all for a DM (or to send a non-string value). In that case the old code would reject the server's response as malformed, and the new code after this commit would accept it because it doesn't look for the field.
The point in this autocomplete UI is to choose the entire new value for the topic input. So we can do that directly, without the more complicated logic that parallels what we need in the more complicated case of autocomplete in the content input. This is NFC as far as the text value of the input is concerned. I believe it's probably NFC entirely, because the remaining aspects of TextEditingValue -- the selection and the composing region -- are probably clobbered anyway by the next line's giving focus to the content input. (The new test already passes before this change.)
We'll use this to make a type-level distinction between a string that's being used to name a topic, and just any old string. That will help us in particular with zulip#1250 the "general chat" feature, where the way we show a topic name to the user stops being necessarily just the same string that it appears as in the API. The next phase will be to migrate a bunch of places in our code to refer to TopicName when that's what they mean, instead of just String. During this phase, a TopicName can be freely used as a String, but not vice versa. So we'll do the migrations mostly in order of the data flow, from upstream to downstream. That will allow us to do them over a series of individually coherent commits, with a minimum of occasions where we temporarily introduce a conversion that won't be needed in the final result. That means: first, test data; then topics returned from the API to our code; then our internal models; then back to the API, this time for topics passed to the API from our code. After we have the type in place all over where it belongs, we'll start making use of the distinction, and then enforcing it.
These are being passed to test helpers which we'll leave as consuming String instead of TopicName, for convenience in other test cases. At these call sites the values were coming from fields that are or will become TopicName, though; so when that happens and we make TopicName no longer implicitly convertible to String, these will need to be explicitly String instead.
We do this all together in one commit, to avoid trying to disentangle the graph of how topics get passed around among the various parts of our model.
This completes the conversion to use the TopicName type in the places where it belongs. Next, we'll start making use of this new type distinction: instead of using a TopicName implicitly as a String, we'll introduce members corresponding to the different ways a topic name is used which are going to start having different behavior in the future. After that's complete, we can start enforcing the distinction in order to catch accidental future implicit uses as a String.
This isn't part of the Zulip server API -- it's entirely between the `buildUrl` and `parseUrl` of this class, and we could make up whatever encoding we like. But the API name of the topic is the natural thing to use.
Each of these call sites will need to be updated for zulip#1250. Those sites can now be found by listing the references to this getter. As a bonus the type-checker will point out most of the places that need updates, because this getter will become nullable. (The exceptions are where it's used in a string interpolation, because `null.toString()` is valid and returns "null".)
We're now explicitly using apiName, canonicalize, or displayName in each of the places where we had been implicitly treating a topic name as a String. Let the type-checker catch any future regressions on that front, by removing "implements String". More precisely, I *think* we've covered each such place. The loophole is that Object methods can still be called, including toString, so an interpolation like "${channelName} > ${topic}" won't be flagged by the analyzer. I found some such interpolations with a grep, but that's necessarily heuristic and there could be more. So the analyzer won't give us quite as much help here as we'd like; but it'll give quite a bit, and this is the most I see how to do. Without the "implements String", a TopicName value can no longer be implicitly converted to String or have String methods called on it. Instead the type-checker will require any code that has such a value to call one of the members we've declared on TopicName (or a member of Object) in order to do anything with it. That way the code will be explicit about whether it needs the API name, or the display name, or the canonicalized form for making equality comparisons. And that in turn will enable us to make "display name" behave differently from "API name", for zulip#1250 the "general chat" feature, with a reliable way of tracking down which sites need which version.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Toward #1250.
This PR introduces a new type
TopicName
as a Dart "extension type":https://dart.dev/language/extension-types
At runtime it's just a String — so there's no performance cost to it — but the type-checker tracks the distinction.
This lets us readily find all the spots in the code that need to adapt to show "general chat" for #1250: it's exactly the use sites of the new
TopicName.displayName
. (With one possible loophole involving string interpolation; see the last commit message below.)This series is nearly NFC; each commit is NFC except the first two prep commits, which are nearly so.
Selected commit messages
api: Explicitly ignore topics on DMs
The topic/subject field on DMs carries no information in the current
API, and we already never actually look at it, as demonstrated by
the lack of changes in the rest of the tree in this commit. Clean
up the type definitions in the API a bit by making that explicit.
This is NFC except in the case where a server were to not send this
field at all for a DM (or to send a non-string value). In that case
the old code would reject the server's response as malformed, and
the new code after this commit would accept it because it doesn't
look for the field.
api [nfc]: Introduce TopicName extension type
We'll use this to make a type-level distinction between a string
that's being used to name a topic, and just any old string.
That will help us in particular with #1250 the "general chat"
feature, where the way we show a topic name to the user stops being
necessarily just the same string that it appears as in the API.
The next phase will be to migrate a bunch of places in our code
to refer to TopicName when that's what they mean, instead of just
String.
During this phase, a TopicName can be freely used as a String, but
not vice versa. So we'll do the migrations mostly in order of the
data flow, from upstream to downstream. That will allow us to do
them over a series of individually coherent commits, with a minimum
of occasions where we temporarily introduce a conversion that won't
be needed in the final result.
That means: first, test data; then topics returned from the API to
our code; then our internal models; then back to the API, this time
for topics passed to the API from our code.
After we have the type in place all over where it belongs, we'll
start making use of the distinction, and then enforcing it.
api [nfc]: Use TopicName type across request parameters
This completes the conversion to use the TopicName type in the
places where it belongs.
Next, we'll start making use of this new type distinction: instead
of using a TopicName implicitly as a String, we'll introduce members
corresponding to the different ways a topic name is used which are
going to start having different behavior in the future.
After that's complete, we can start enforcing the distinction in
order to catch accidental future implicit uses as a String.
api [nfc]: Introduce TopicName.apiName, and use for making API requests
api [nfc]: Use TopicName.apiName for constructing narrow links
notif [nfc]: Use TopicName.apiName for encoding NotificationOpenPayload
This isn't part of the Zulip server API -- it's entirely between the
buildUrl
andparseUrl
of this class, and we could make up whateverencoding we like. But the API name of the topic is the natural thing
to use.
api [nfc]: Factor out TopicName.canonicalize
api [nfc]: Introduce TopicName.displayName, and use where needed
Each of these call sites will need to be updated for #1250. Those
sites can now be found by listing the references to this getter.
As a bonus the type-checker will point out most of the places that
need updates, because this getter will become nullable. (The
exceptions are where it's used in a string interpolation, because
null.toString()
is valid and returns "null".)api [nfc]: Seal the TopicName migration by making non-transparent
We're now explicitly using apiName, canonicalize, or displayName in
each of the places where we had been implicitly treating a topic name
as a String. Let the type-checker catch any future regressions on
that front, by removing "implements String".
More precisely, I think we've covered each such place. The
loophole is that Object methods can still be called, including
toString, so an interpolation like "${channelName} > ${topic}" won't
be flagged by the analyzer. I found some such interpolations with a
grep, but that's necessarily heuristic and there could be more.
So the analyzer won't give us quite as much help here as we'd like;
but it'll give quite a bit, and this is the most I see how to do.
Without the "implements String", a TopicName value can no longer be
implicitly converted to String or have String methods called on it.
Instead the type-checker will require any code that has such a value
to call one of the members we've declared on TopicName (or a member
of Object) in order to do anything with it. That way the code will
be explicit about whether it needs the API name, or the display
name, or the canonicalized form for making equality comparisons.
And that in turn will enable us to make "display name" behave
differently from "API name", for #1250 the "general chat" feature,
with a reliable way of tracking down which sites need which version.