GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — String literal types by DanielRosenwasser · Pull Request #5185 · microsoft/TypeScript

Conversation

@DanielRosenwasser

@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) commented Oct 8, 2015 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

String Literal Types

This pull request adds reasonable support for string literal types as proposed in #1003.

A string literal type can be described by a string literal type node, which is otherwise the same lexically as the StringLiteral production in ECMAScript.

A string literal type is a type whose expected value is a string with textual contents equal to that of the string literal type. In other words, there is no difference between "hello'world" and 'hello\'world'.

let tradition: "hello world" = 'hello world';

let quotes: "hello'world" = 'hello\'world';

String literal types can be used in conjunction with union types to describe a finite set of possible string values:

type CardinalDirection = "North"
                       | "East"
                       | "South"
                       | "West";

function move(distance: number, direction: CardinalDirection) {
    // ...
}

A string literal type can be considered a subtype of the string type. This means that a string literal type is assignable to a plain string, but not vice-versa.

Because of this distinction, string literals no longer always have the type string. Instead, a string literal's type is informed by its contextual type. The rules are the following:

  • If a string literal is contextually typed a string literal type, then it returns a string literal type with the textual contents of the string literal.
  • If a string literal is contextually typed by a union type containing a string literal type, then it returns a string literal type with the textual contents of the string literal.
  • Otherwise, the literal is typed as string.
let a: "foo" | number = "foo"; // valid
let b: "bar" | "baz" = "foo";  // invalid

Note that intersections of string literals do not affect the type; "baz" will have the string type below, and the following assignment will fail:

let c: "baz" & { prop: number } = "baz"; // invalid

String literal types have the same apparent properties as string (i.e. the String global type), and are mostly compatible with operators like + in the same way that a string is:

var HELLO: "HELLO" = "HELLO";
var WORLD: "WORLD" = "WORLD";
var hello = HELLO.toLowerCase();  // type: string
var HELLOWORLD = HELLO + WORLD; // type: string

Specialized signatures are also compatible with string literal types:

interface HTMLElement {
    addEventListener(type: "click", listener: (ev: MouseEvent) => any, useCapture?: boolean): void;
    addEventListener(type: "focus", listener: (ev: FocusEvent) => any, useCapture?: boolean): void;
    addEventListener(type: "drag", listener: (ev: DragEvent) => any, useCapture?: boolean): void;
}

namespace EventType {
    export const Click: "click" = "click";
    export const Focus: "focus" = "focus";
    export const Drag: "drag" = "drag";
}

let element: HTMLElement = /*...*/;
element.addEventListener("click", ev => {
    // 'ev' is correctly inferred to be a 'MouseEvent'
    console.log(`x: '${ev.x}', y: '${ev.y}'`);
}
// also works
element.addEventListener(EventType.Click, ev => {
    // 'ev' is correctly inferred to be a 'MouseEvent'
    console.log(`x: '${ev.x}', y: '${ev.y}'`);
}

Open Questions

  • We could introduce a new form of type guards so that equality checks with string literals narrow strings to their respective string literals. Is this worthwhile?
  • Right now, signatures are only specialized if they have a parameter whose type is a string literal type. Should we expand this so that signatures are specialized if any of their parameters' types contain a string literal type within them?
  • Given that we have the textual content of a string literal type, we could reasonably perform property lookups in an object. I think this is worthwhile to consider. This would be even more useful if we performed narrowing.
  • Right now a string literal's type is only informed by a contextual type. Should const declarations imply that a string literal type should be used?
  • We currently allow number to be assignable to enum; however, this is due to how frequently people use flags in enums. Should we allow string to be assignable to a string literal type? I am personally against this idea.
  • Should the same typing rules for string literals apply to no-substitution template strings?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

jeanpchr reacted with hooray emoji remojansen and jeanpchr reacted with heart emoji
All reactions
  • 🎉 1 reaction
  • ❤️ 2 reactions
Daniel Rosenwasser (DanielRosenwasser) added 21 commits September 25, 2015 17:38
@DanielRosenwasser
Added tests.
311a0cf
@DanielRosenwasser
Added test for string literal types in type arguments.
911e907
@DanielRosenwasser
Accepted baselines.
f04cc39
@DanielRosenwasser
Make string literals valid constituent types nodes in the parser.
191be4f
@DanielRosenwasser
Accepted baselines.
84786d8
@DanielRosenwasser
Make string literals valid types in type lists.
dc0e368
@DanielRosenwasser
Accepted baselines.
8891fba
@DanielRosenwasser
Added test for string types in tuples.
82545ce
@DanielRosenwasser
Accepted baselines.
ed927d8
@DanielRosenwasser
Use normalized text for text on string literal types.
a3e7ccb
@DanielRosenwasser
Amended fourslash tests to expect double quotes.
20c2c4e
@DanielRosenwasser
Accepted baselines.
87f2957
@DanielRosenwasser
Capture compatible contextual types for unions containing string lite…
f721971
…rals.
@DanielRosenwasser
Accepted baselines.
7b4e94d
@DanielRosenwasser
Separated the concept of apparent types from contextual types for str…
d8d72aa
…ing literal types.

In most cases, expressions are interested in the apparent type of the
contextual type. For instance:

    var x = { hasOwnProperty(prop) { /* ... */ };

In the above, 'prop' should be contextually typed as 'string' from the
signature of 'hasOwnProperty' in the global 'Object' type.

However, in the case of string literal types, we don't want to get the
apparent type after fetching the contextual type. This is because the
apparent type of the '"onload"' string literal type is the global 'String'
type. This has adverse effects in simple assignments like the following:

    let x: "onload" = "onload";

In this example, the right-hand side of the assignment will grab the type
of 'x'. After figuring out the type is "onload", we then get the apparent
type which is 'String'. This is problematic because when we then check the
assignment itself, 'String's are not assignable to '"onload"'s.

So in this case, we grab the contextual type *without* getting its
apparent type.
@DanielRosenwasser
Accepted baselines.
315b06d
@DanielRosenwasser
Fixed issue in test.
4b736da
@DanielRosenwasser
Accepted baselines.
fd5dec4
@DanielRosenwasser
Added more tests.
f7a6ac7
@DanielRosenwasser
Accepted baselines.
a440f06
@DanielRosenwasser
Added fourslash test.
d2e2a55
@msftclas Microsoft Contribution License Agreements (msftclas) added the cla-not-required label Oct 8, 2015
Daniel Rosenwasser (DanielRosenwasser) added 6 commits October 8, 2015 15:18
@DanielRosenwasser
Merge branch 'master' into stringLiteralTypes
6e3343c
@DanielRosenwasser
Accepted post-merge baselines.
74ac57d
@DanielRosenwasser
Return the string literal type itself instead of the union type.
61ece76
@DanielRosenwasser
Accepted baselines.
84b64c4
@DanielRosenwasser
Semicolon.
3788254
@DanielRosenwasser
Linting.
ebc47d5
@danquirk

Dan Quirk (danquirk) commented Oct 8, 2015

Copy link
Copy Markdown
Member

Very cool. Definitely seems highly desirable to find a way to do narrowing/type-guard sort of behavior here:

type CardinalDirection = "North" | "East" | "South" | "West";

function move(distance: number, direction: CardinalDirection) {
    if(direction === "North") {
        // ok
    }
    else if(direction === "SouthWest") {
        // oops, no error at the moment
    }
}
Zoxive reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

jbondc
jbondc reviewed Oct 8, 2015
View reviewed changes
Comment thread src/compiler/checker.ts Outdated

@jbondc Jon (jbondc) Oct 8, 2015

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Not that I want to sound negative but for every string literal, this gets called.

It's a huge slowdown.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@RyanCavanaugh Ryan Cavanaugh (RyanCavanaugh) Oct 9, 2015

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

What kind of numbers are you seeing (and on what codebase)?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@tinganho Tingan Ho (tinganho) Oct 9, 2015

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

If you consider that every string has on average 5 bytes, you're making 1-5 integer checks for every comparison:

I think the VM already optimize for this?
http://jsperf.com/string-vs-int-comparison-1

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@RyanCavanaugh Ryan Cavanaugh (RyanCavanaugh) Oct 9, 2015

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

It's useless to speculate about what's fast and what's not. Measure it.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@jbondc Jon (jbondc) Oct 9, 2015

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Meh. Measure it in 1 version of Chrome, it changes in the next. You measure it, I've said what I had to say.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@tinganho Tingan Ho (tinganho) Oct 12, 2015

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Jon (@jbondc) 👍 for the benchmark test.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) Oct 20, 2015

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I don't think the original issue was so terrible. You only ran into this case when you were contextually typed by a type with a string literal constituent to begin with. In practice, this is not frequently encountered.

However, Jon (@jbondc), I think you'll like the the current implementation a lot better. We now only create a string literal type if the constituent types have a string literal type, not just if their content is equal.

Furthermore, the types are cached in a map of strings to string literal types. Whenever testing the assignability of two string literal types, reference equality kicks in (which is fast).

Additionally, error messages are slightly better because you have a specific literal type to report (i.e. Type '"Foo"' is not assignable to '"Bar"'. instead of Type 'string' is not assignable to '"Bar"'.).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) Oct 20, 2015

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Jon (@jbondc) at worst that's probably much faster than trying to resolve the 100 different overloads. Though, I'm going to try to avoid speculating. I wasn't seeing a real perf hit in our benchmarks even with the initial change.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@Artazor Anatoly Ressin (Artazor) mentioned this pull request Jan 30, 2016
Proposal: Get the type of any expression with typeof #6606
Closed
@smithkl42 Ken Smith (smithkl42) mentioned this pull request Feb 6, 2016
Error after installing TS 1.8 beta through Nuget #6929
Closed
@joeskeen Joe Skeen (joeskeen) mentioned this pull request Feb 10, 2016
Errors when using TS 1.8 string literal types palantir/tslint#966
Closed
@franza

Yusup Abdullaev (franza) commented Mar 2, 2016

Copy link
Copy Markdown

I assume that it was mentioned somewhere in thread and I didn't find it. Can I use string literal constraint for names of properties? I don't see that it works right now (playground)

type Columns = 'name' | 'email' | 'age';

interface TableRow {
    [key: Columns]: string;
}
TiuSh and nlieb reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@kitsonk

Kitson Kelly (kitsonk) commented Mar 2, 2016

Copy link
Copy Markdown
Contributor

Currently indexers can only be string | number. Other types have been brought up before. See #2049, #1863 and mainly #6080.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) added the Domain: Literal Types Unit types including string literal types, numeric literal types, Boolean literals, null, undefined label Mar 2, 2016
@zivni Ziv (zivni) mentioned this pull request Mar 20, 2016
Rename and fine all references of Typescript's string literal values microsoft/vscode#4469
Closed
@dbaeumer Dirk Bäumer (dbaeumer) mentioned this pull request Mar 21, 2016
Rename and fine all references of Typescript's string literal values #7611
Closed
@whatisaphone

whatisaphone (whatisaphone) commented Apr 4, 2016

Copy link
Copy Markdown

I'm really hoping to one day be able to quickly write ADTs in a succinct manner. something like this:

type ListMutation<T> =
    { type: 'insert-one', index: number, value: T } |
    { type: 'remove-range', index: number, count: number };

function apply<T>(mutation: ListMutation<T>) {
    if (mutation.type === 'insert-one')
        console.log(`inserting ${mutation.value}`);
    else if (mutation.type === 'remove-range')
        console.log(`removing ${mutation.count} items`);
}

Today, the compiler is unable to make the jump from mutation.type === 'insert-one' to mutation being guaranteed to have a value property. Is support for this kind of inference on the roadmap?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@DanielRosenwasser

Daniel Rosenwasser (DanielRosenwasser) commented Apr 4, 2016

Copy link
Copy Markdown
Member Author

johnsoft we're looking into it, but right now we need to get a sense of how to make that play well with certain other language features.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@whatisaphone

whatisaphone (whatisaphone) commented Apr 4, 2016

Copy link
Copy Markdown

Good to hear! Is there another issue I could follow that's more closely related to that use-case? I couldn't find anything after a quick search.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@DanielRosenwasser

Daniel Rosenwasser (DanielRosenwasser) commented Apr 5, 2016

Copy link
Copy Markdown
Member Author

johnsoft #6062, #6028, and optionally #7642

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@DanielRosenwasser

Daniel Rosenwasser (DanielRosenwasser) commented Apr 5, 2016

Copy link
Copy Markdown
Member Author

Also, #186. Duh. Can't believe I forgot about that one.

In any case, I am waiting on some work where we'll be modifying the way we do type guards.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@whatisaphone

whatisaphone (whatisaphone) commented Apr 5, 2016

Copy link
Copy Markdown

Great, thanks for the links! I'm really impressed with how the language is evolving.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Aluan Haddad (aluanhaddad) referenced this pull request in dimitrikochnev/systemjs Apr 24, 2016
Dimitri Rosenberg
next iteration systemjs#2
93d5bc1
@aindlq Artem (aindlq) mentioned this pull request Aug 3, 2016
General indexer type #2049
Closed
@vjau

Vincent Jaubert (vjau) commented Aug 28, 2016 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

When i stumbled on this feature i found it a very welcome addition to the language, but after having played a little bit with it, and getting it not to work for everything it throwed at it, i wonder what this is really good for.
Simple exemple that should intuitively work but doesn't.

const testFunc = (stuff:"bar")=>{};
testFunc("bar"); // OK

const stuff = "bar";
testFunc(stuff);// not OK

After two hours pulling my hair around this (my initial code was more complicated), i finally found out you have to write

const stuff = "bar" as "bar";

Being no TS expert but just a normal programmer trying to gain time with the security of a type checking compiler, i find this overly counter-intuitive and complicated.

Sorry if this not the place to discuss this, in the case would you be kind to point me to the correct place ?

Thank you.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@felixfbecker

Felix Becker (felixfbecker) commented Aug 28, 2016

Copy link
Copy Markdown
Contributor

Vincent Jaubert (@vjau) this is expected behaviour. You are relying on type inference. And the infered type of stuff is string. But testFunc only accepts "bar" as a type, not string. When you cast the string to "bar", it works. Most often you will define a type alias with a union of string literal types, and then you can cast to that type alias.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joewood

Joe Wood (joewood) commented Aug 28, 2016

Copy link
Copy Markdown

This is getting off topic - but yeah, I would expect the type of a const x = "bar" to be "bar". I can't see an open issue covering this through, so may want to open something new.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@kitsonk

Kitson Kelly (kitsonk) commented Aug 28, 2016

Copy link
Copy Markdown
Contributor

Joe Wood (@joewood) it is discussed under #10195 and somewhat under #9489 which was closed because it would be a significant breaking change.

Vincent Jaubert (@vjau) there is a better work around (also discussed in #9489) which is to be explicit about the type:

const stuff: 'bar' = 'bar';

There are several types that are "TypeScript only" that don't get inferred, right now, literal types are one of those, tuples are another good example const = [ 'foo', 1 ] will always get inferred as string|number[] instead of [ string, number ].

basarat reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joewood

Joe Wood (joewood) commented Aug 29, 2016

Copy link
Copy Markdown

Kitson Kelly (@kitsonk) The issue in #9489 starts off by talking about the type literal of a const but switches to the issue of implicitly typing React style structures. As Anders Hejlsberg (@ahejlsberg) suggests in that issue, the type of a const could be narrowed to the literal type. I haven't seen a good example of where that would break existing code, unless I'm missing something.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@kitsonk

Kitson Kelly (kitsonk) commented Aug 29, 2016

Copy link
Copy Markdown
Contributor

I haven't seen a good example of where that would break existing code, unless I'm missing something.

I didn't raise or respond to the issue... I am just pointing out that it addresses the bit off topic where you felt it wasn't being discussed elsewhere and might be a better place to continue the discussion.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joewood

Joe Wood (joewood) commented Aug 29, 2016

Copy link
Copy Markdown

Thanks Kitson Kelly (@kitsonk), I posted on that issue you referenced - #9489 (comment)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@DanielRosenwasser

Daniel Rosenwasser (DanielRosenwasser) commented Aug 29, 2016

Copy link
Copy Markdown
Member Author

See my response on the breaking change at #9489 (comment). Just to give some background, I'll write a little bit up here.

After this PR went in, we heavily discussed an alternate approach of using widening to try to stop incorrect comparisons (e.g. "foo" === "bar" is nonsensical, why would we allow it?). Basically the other idea was that all strings start out with a string literal type and then are widened to an appropriate string type at a location where a binding is mutable. This sounds super intuitive.

The discussion started here: #6167
And the PR went out here: #6554

The problem is that too much of the time, the type would vanish too quickly or stick around too long. The behavior was looking generally inappropriate and every time we thought we had the perfect idea, it wasn't good enough for some other case. It became a game of whack-a-use-case.

This original PR was nice because:

  • It didn't break anything.
  • Tuple types already used the same concept (i.e. using the contextual type to "enhance" an expression's type).

The comparability problem was solved with #5517 and #6196 anyway, so the only real issue is that people want it to be easier to infer literal types, which I absolutely get.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joewood

Joe Wood (joewood) commented Aug 29, 2016

Copy link
Copy Markdown

Thanks for the detailed response Daniel Rosenwasser (@DanielRosenwasser).

In my mind a string literal type of one value is an immutable type, and it's paradoxical to assign that type to a mutable variable (at least, semantically). Implicit type inference for literals should therefore always widen when typing a mutable variable or parameter, and likewise an immutable variable should be narrowed as much as possible.

It's a shame the #6554 couldn't be made to work, because it kind of seems counter-intuitive right now that a constant expression's type is different to its implicit type used for immutable binding.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@felixfbecker

Felix Becker (felixfbecker) commented Aug 29, 2016

Copy link
Copy Markdown
Contributor

Joe Wood (@joewood) it is not paradox, as long as they have the same type.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joewood

Joe Wood (joewood) commented Aug 29, 2016

Copy link
Copy Markdown

Felix Becker (@felixfbecker) right, but I mean it's essentially at that point semantically immutable. You can change its value to another string of the same value.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joewood Joe Wood (joewood) mentioned this pull request Aug 30, 2016
More accurate typing of Object.assign and React component setState() #6613
Closed
@coveralls

Coveralls (coveralls) commented Sep 8, 2017

Copy link
Copy Markdown

Coverage Status

Changes Unknown when pulling ea4e21d on stringLiteralTypes into ** on master**.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@microsoft Microsoft (microsoft) locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

Domain: Literal Types Unit types including string literal types, numeric literal types, Boolean literals, null, undefined

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

20 participants

@DanielRosenwasser @danquirk @weswigham @JsonFreeman @vladima @DickvdBrink @jbondc @tejacques @mhegazy @zpdDG4gta8XKpMCd @felixfbecker @kitsonk @domoritz @basarat @franza @whatisaphone @vjau @joewood @coveralls @tinganho
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.