Skip to content
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

Allow declaration declare within conditionals #25570

Closed
1 of 4 tasks
ganeshkbhat opened this issue Jul 11, 2018 · 12 comments
Closed
1 of 4 tasks

Allow declaration declare within conditionals #25570

ganeshkbhat opened this issue Jul 11, 2018 · 12 comments
Labels
Duplicate An existing issue was already created

Comments

@ganeshkbhat
Copy link

ganeshkbhat commented Jul 11, 2018

Search Terms

declare and variable declaration (for issues)

Suggestion

Allow declaration inside conditionals like below:

if (conditional) {
     declare var variable: any | <T>
} 

Use Cases

Lets take an example of Web Worker support from browser. If the Web Worker (window.Worker) is not available I will switch to a polyfill that creates a window.PseudoWorker object option. The pseudoworker is always loaded irrespective of browser support.

In my .ts class, I will create the following:

declare var Worker: Worker | undefined;
if (!Worker) {
     declare var PseudoWorker: any; // this fails
}

class y {
  worker: any;
  constructor() {
    if (!Worker) {
         this.worker = new PseudoWorker('myurl.js');
    } else {
         this.worker = new Worker('myurl.js');
    }
  }
}

An alternative way to approach the following is:

declare var PseudoWorker: Worker | any;

But the issue with this is, I am loading a polyfill with PseudoWorker all the time in the global context irrespective of the fact that Web Worker is supported or not (window.Worker object present or not). Since the Object is always going to be available there is a clash in the object names used.

This is however different from the #23602 issue by the way that:

  • The declaration in the global context stays for pseudoworker and worker for supported browsers OR just pseudoworker for unsupported browsers
  • The declaration of pseudoworker object to be used inside .ts file module should be optional based on presence of an object based on browser support
  • There will be global naming issues (unexpected overriding) if the conditional declaration is not there (incase of pseudoworker when the browser worker support or window.Worker object is available)

An alternate recommendation based on the 23602 issue may be

declare? (var Worker: Worker) : (var PseudoWorker: any); (updated from declare? var Worker: Worker || var PseudoWorker: any;)

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. new expression-level syntax)
@mhegazy
Copy link
Contributor

mhegazy commented Jul 11, 2018

Looks like a duplicate of #819 and similar use case as #23602.

Seems like the request is to just allow an override for a variable or a property declaration. correct?

@mhegazy mhegazy added the Duplicate An existing issue was already created label Jul 11, 2018
@ganeshkbhat
Copy link
Author

ganeshkbhat commented Jul 11, 2018

No its not a duplicate of #819 . Its a different use case. In case of #23602 could be partly same. However, #23602 addresses only creation/declaration of the variable in case the type (changed from object) is/not present.

Here, in this use case (conditional declaration), I am requesting declaration of a variable if case 1 and declaration of another variable in case 2.

This is for declare? (var Worker: Worker) : (var PseudoWorker: any); This could be extension of #23602 .

But the second use case of allowing conditional declaration is to allow declaration based on a dependent case - declaration of a variable if case 1 and declaration of another variable in case 2 where case 1 object is not defined.

declare var Worker: Worker | undefined;
if (!Worker) {
  declare var PseudoWorker: any;
}

This is the actual request - presumably. But the optional declaration [declare? (var x:<T> ) : (var y: <T>)] above solves both, I think. Thoughts?

@mhegazy
Copy link
Contributor

mhegazy commented Jul 11, 2018

i am not sure i understand what you mean by "variable if case 1 and declaration of another variable in case 2." are you looking for dependent types?

@ganeshkbhat
Copy link
Author

ganeshkbhat commented Jul 12, 2018

declare var variable within a conditional block (case). You mày have dependant types or dependant conditions. Both ways. That's what makes me believe declare? (var x:<T>) : (var y:<T>) might solve both cases of selective declaration (eg: #23602 based on type) as well as conditional/block scoped declaration (eg: #25570 based on object not 'necessarily' type). Now, I think we can add this issue's use case #25570 to #23603 to make a new proposal to meet the mentioned use cases.

@ganeshkbhat
Copy link
Author

ganeshkbhat commented Jul 12, 2018

I am unsure how easy or difficult it will be to implement this but my thoughts (thinking aloud). My requirement was allowing declaration within conditional block:

"declaration of a variable if conditional case 1 (type or global scope object) and declaration of another variable in conditional case 2 (type or global scope object)"

Use case:

if (xObj === 1) {
     declare var a: <T>;
} else if (yObj === 2) {
     declare var b: <T>;
}
  • Proposed:
    Allow conditional declaration as above.
if (xObj === 1) {
     declare var a: <T>;
} else if (yObj === 2) {
     declare var b: <T>;
}

Use case:

if (xObj === 1) {
     declare var a: <T>;
} else {
     declare var b: <T>;
}
  • Proposed:
    Allow conditional declaration as above.
if (xObj === 1) {
     declare var a: <T>;
} else {
     declare var b: <T>;
}

Use case:

if (<SOMETYPE>) {
     declare var a: <SOMETYPE>;
} else if (!<SOMETHIRDTYPE>) {
     declare var b: <SOMEFOURTHTYPE> | <T>;
}
  • Proposed:
declare? (var a: <SOMETYPE>) : false;
if (!<SOMETHIRDTYPE>) {
    declare var b: <SOMETHIRDTYPE> | <T>;
}

OR

declare? (var a: <SOMETYPE>) : false;
declare? false: (var b: <SOMETHIRDTYPE> | <T>);

OR

if (<SOMETYPE>) {
     declare var a: <SOMETYPE>;
} else if (!<SOMETHIRDTYPE>) {
     declare var b: <SOMEFOURTHTYPE> | <T>;
}

Use case:

if (<SOMETYPE>) {
     declare var a: <SOMETYPE>;
} else if (<SOMETHIRDTYPE>) {
     declare var b: <SOMETHIRDTYPE>;
}
  • Proposed:
declare? (var a: <SOMETYPE>) : false;
declare? (var b: <SOMETHIRDTYPE>) : false;

OR

if (<SOMETYPE>) {
     declare var a: <SOMETYPE>;
} else if (<SOMETHIRDTYPE>) {
     declare var b: <SOMETHIRDTYPE>;
}

"declaration of a variable if conditional case 1 (type or global scope object), and declaration of another variable in conditional case 2 (type or global scope object) where conditional case 1 object/type is not defined."

Use case:

if (<SOMETYPE>) {
     declare var a: <SOMETYPE>;
} else {
     declare var b: <SOMEOTHERTYPE>;
}
  • Proposed:
    declare? (var a: <SOMETYPE>) : (var b: <SOMEOTHERTYPE>)

Use case:

declare var a: <SOMETYPE> | undefined;
if (!a) {
     declare var b: <SOMEOTHERTYPE> | <T>;
} 
  • Proposed:
declare var a: <SOMETYPE> | undefined;
if (!a) {
     declare var b: <SOMEOTHERTYPE> | <T>;
}

Use case:

// Some global OR local object a
if (!aGobalORlocalObject) {
     declare var b: <SOMEOTHERTYPE> | <T>;
} 
  • Proposed:
    Allow conditional declaration as above.
// Some global OR local object a
if (!aGobalORlocalObject) {
     declare var b: <SOMEOTHERTYPE> | <T>;
} 

This is different from #23602 where "selective declaration is made based on presence of type or object"

Use Case in #23602:

if (!<SOMETYPEOROBJECT>) {
     declare var a: <SOMETYPEOROBJECT>;
}

AND

declare var a: <SOMETYPEOROBJECT> | undefined; // a is declared and is undefined

Proposed in #23602:

declare? var a: <SOMETYPEOROBJECT>; // a should not be declared

@kitsonk
Copy link
Contributor

kitsonk commented Jul 12, 2018

It sounds like you are trying to solve #21316 a different way. How would #21316 not solve the problems you are talking about?

@ganeshkbhat
Copy link
Author

ganeshkbhat commented Jul 12, 2018

Yes, quite close. But I believe issue #21316 does not address some above use cases. Say, I dont have an interface or a type and it is an object presence or availability I am checking for, in the global's object context.

Here, lets say

  • we are checking if the Worker Type/Object exists then we assign type Worker to declared variable Worker else we assign the type any. This case the declared variable Worker may have Worker object assigned during runtime and PseudoWorker object will never be declared and assigned to any variable. In case it is needed, we will have compiletime errors.
  • we are checking if the Worker Type/Object exists then we assign type Worker to declared variable PseudoWorker else we assign type any (Conditional types #21316). This case declared variable PseudoWorker may have Worker object assigned during runtime or PseudoWorker object to be assigned during runtime based on whether the PseudoWorker polyfill gets loaded before or very late in the app initialization.
  • we are checking if the Worker Type/Object exists then we assign type Worker to declared variable Worker else we assign type undefined to Worker and declare PseudoWorker with type any. This case allows the globally named PseudoWorker object, which is a polyfill object, to take the value for PseudoWorker.

Would the issue #21316 suffice? I believe not. The first two will be dependent on whether the polyfill object was loaded or not - when the browser supports Worker Object (and polyfill is not needed). Note that the polyfill of PseudoWorker is there loaded always. Second, in #21316 a conditional type assignation also does not allow for not declaring a variable at all if it is supposed to be undefined (#23602 declare? var x: Worker). It also does not address the proposal of selectively declaring a variable Worker or PseudoWorker based on any other condition like thirdVariableObject definition or meeting a condition, OR on a condition where Worker object is available in the browser.

Hence the need for proposals (ironically, which I did not notice, my requirement seems to be making two change proposals - let me know if another issue is needed or you can tag it up for two features):

// declaring a variable itself conditionally
declare? var Worker: Worker;

AND

// declaring conditional types
declare var Worker: Worker? Worker : undefined;
declare var PseudoWorker: Worker? undefined : any;

AND

// declaring a variable based on presence of a type/object
declare var Worker: Worker | undefined;
if (!Worker) {
     declare var PseudoWorker: any;
}

AND

// declaration syntax sugar and conditional declaration of different variables
declare? (var Worker: Worker) : (var PseudoWorker: any);

AND

// declaring a variable based on presence of a global object or meeting a condition inside a conditional block
// Some global object a
if (a !== 'TestUseCase') {
   declare var someGlobalVarName: string;
} 

@mhegazy
Copy link
Contributor

mhegazy commented Jul 12, 2018

Looks like all these use cases boil down to conditional types. let's not get stuck on the syntax or how to declare these variables. This request needs the type system to process and understand dependent types (possibly infer them from declarations/conditional statements).

@mhegazy
Copy link
Contributor

mhegazy commented Jul 12, 2018

I do not think we have a specif issue tracking dependent types at the moment. there has been discussions about them in different issues. if you want to log a new suggestions for dependent types that would be great.

@ganeshkbhat
Copy link
Author

ganeshkbhat commented Jul 13, 2018

Yes, I understand. That is something I have not considered in a holistic manner, frankly, while creating the issue. My bad. I was only considering simple conditionals for declaration (whereas dependency types or related values may take different structure altogether) since it was needed for a context of a project. May be you could put your thoughts as well based on requests and your experience. Technically #21316 is a dependency type case; and #25570 's main proposal of conditional block declaration may not really face the same complexity, and allows for more flexibility during creation of the language definition without a lot of use case checks (again thats my view).

The only dependency I was considering was presence or global or local definitions within conditions. That could be fairly easy to take forward than a bunch of dependency value or type checks. The reason, why my main request was actually allowing conditional block declaration (which can be done relatively easily than a full supported set of features for dependency types inside .ts). Something like this:

Use case #23602

if (!!Window) {
     declare var Window: Window;
} 

Use case #25570

if (!!Worker) {
     declare var Worker: Worker;
} 

OR

declare var Worker: Worker | undefined;
if (!Worker) {
     declare var PseudoWorker: any;
} 

OR

if (!window.Worker) {
     declare var PseudoWorker: any;
} 

OR

if (a === 12) {
     declare var PseudoWorker: any;
} 

OR

Use case #21316

// Use case dependency type #21316 declare var Worker: Worker ? Worker : any;
// Can be rewritten as below

declare var Worker: Worker | undefined;
if (!Worker) {
     declare var PseudoWorker: any;
} 

// However this does not resolve the problem of not declaring a variable Worker after all
// Which is resolved by #23602 by not declaring them at all - 
// declare? var Worker:Worker;

// Which is extended by dependency type inception 
// declare? (var PseudoWorker: Worker) : (var PseudoWorker: any). 
// The value is prone for unneeded overriding and incorrect behavior which is the complexity we discussed (and may be during language implementation as well) and better declared as 
// declare? (var Worker: Worker) : (var PseudoWorker: any). 

// Or as mentioned in #21316 like this
// declare var PseudoWorker: any; declare var Worker:Worker ? Worker: PseudoWorker;


The ways of implementing (my referred) use case can be done in different ways but other use cases may be left out. However, this does not change the language implementation complexity for dependency types. All these use cases will have to be considered holistically to implement multiple declaration implementations rather than one single declaration implementation for different/all issue numbers since some use cases may be left out or left unaddressed. So, technically, all these issues are different language functionality requests rather than a single large set of duplicates.

I can raise a separate issue for this, if you believe you can consider this as a feature for future. But, considering we will implement it, creating a single issue for dependency type and then following all others with duplicates tag for different language functionality implementations may be oppressive than beneficial (like in this issue's original recommendation of allowing conditional block declaration). Even if they fall into a single generic "dependency types" category, all these address different concerns and use cases by ways of alternative or different implementations. "Dependency types" should actually be a tag providing for multiple language functionalities/implementations, and not be "duplicates" for a single large issue/concern, is what I believe. May be you should consider duplicates tag for issues/requests of the same method of implementation of dependency types.

I can, for sure, create an issue for conditional dependent type based variable declaration alternative (addressing other use cases) - declare? (var Worker:Worker) : (var PseudoWorker: any).

Just wanted to highlight, the declare? (var Worker:Worker) : (var PseudoWorker: any) single statement can also be written as:

declare var window: Window;
if (!!window.Worker) {
declare var Worker:Worker;
} else {
declare var PseudoWorker: any;
}

OR, you can get extensive by adding your own declarative conditions:

declare var window: Window; // May not be needed
if (!!window) {
// or directly use !!Window
     declare var Worker:Worker;
}
if (!!window && (typeof PseudoWorker === 'object') && AddSomeOtherConditionIfNeeded === 12 ) {
     declare var PseudoWorker: any;
}
interface MyNewType {
     a: string;
}
if (!!MyNewType) {
     declare var PseudoWorker: any;
}
if (AddSomeOtherConditionIfNeeded === 12) {
     declare var Worker: any;
}

I definitely believe dependency of types and values can be difficult to implement in different test cases. But conditional block declaration may not be as complex. What are your thoughts on the same?

@ganeshkbhat
Copy link
Author

Here is another reason why conditional types #21316 will be different from #25570:

// Usage in classes and functions where as #25570 just addresses the declaration part.

type DeepReadonly<T> =
    T extends any[] ? DeepReadonlyArray<T[number]> :
    T extends object ? DeepReadonlyObject<T> :
    T;

interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}

type DeepReadonlyObject<T> = {
    readonly [P in NonFunctionPropertyNames<T>]: DeepReadonly<T[P]>;
};

function f10(part: DeepReadonly<Part>) {
    let name: string = part.name;
    let id: number = part.subparts[0].id;
    part.id = part.id;  // Error
    part.subparts[0] = part.subparts[0];  // Error
    part.subparts[0].id = part.subparts[0].id;  // Error
    part.updatePart("hello");  // Error
}

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants