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

Add feature to re-declare variable #37771

Open
5 tasks done
dguttman-jacada opened this issue Apr 3, 2020 · 5 comments
Open
5 tasks done

Add feature to re-declare variable #37771

dguttman-jacada opened this issue Apr 3, 2020 · 5 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@dguttman-jacada
Copy link

Search Terms

redeclare variable, re-declare variable, augment variable, merge variable declaration, declaration override

Suggestion

define a redeclare keyword which allows re-declaration of a typed variable.
e.g.

redeclare var foo: {
  prop: string
};

Use Cases

When an external library declares a variable, e.g.

declare var x: {
  prop: string;
};

The redeclare keyword can be used somewhere else to override the declaration:

redeclare var x: number;

or augment it:

redeclare var x: typeof x & {
  augmentedProp: number;
}

This would allow to type the pure JavaScript case of:

// lib.js
var x = {
  prop: "foo"
};

// app.js
x.augmentedProp = 10;

console.log(x.prop, x.augmentedProp);

Examples

declare var URLSearchParams: {
    prototype: URLSearchParams;
    new(init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams;
    toString(): string;
};

defined in lib.dom.d.ts

to extend the variable with additional static methods to URLSearchParams is not possible currently (see here).

The feature can be used in the following way:

redeclare var URLSearchParams: typeof URLSearchParams & {
  fromObject(obj: object): URLSearchParams;
};

if (!URLSearchParams.fromObject) {
  URLSearchParams.fromObject = function(obj: object): URLSearchParams { /* implementation */  }
}

Checklist

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. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@DanielRosenwasser
Copy link
Member

I guess my question is what happens if two variables did this?

declare var hello: string | number;

redeclare var hello: number;
redeclare var hello: string;

@dguttman-jacada
Copy link
Author

@DanielRosenwasser
my answer would be:

  1. depending on context of execution flow . Even today, typescript knows that for:
    function foo(input: string | number): string {
      if (typeof input === "string") {
        return input; // here input is string
      } else {
        return number.toString(); // here input is number
      }
    } 
    so in your example, hello would end up a string (if the code is processed sequentially)
  2. It makes sense for the redeclare to be scoped, so if the variable was redeclared within a function body (or any other relevant scope), the redeclaration would be valid for the scope of the function body. e.g for an express app:
    app.get("/", (req, res, next) => {
      redeclare req: typeof req & { myProp: string };
      req.myProp = Guid.generate();
      asyncOp((res) => {
        res.send(res.req.myProp);
      });
    });
    
    req.myProp = ""; // error - req does not have a 'myProp' property
  3. in JavaScript it is also possible to do:
    // file1.js
    var hello = 123; // essentially declare it as a number, would work with declare var hello: string | number;
    
    // file2.js
    var hello = "foo"; // essentially declare it as a string, would work with declare var hello: string | number;
    
    // file3.js
    var hello = false; // essentially declare it as a boolean, would *not* work with the above and would need to be redeclared - in most cases: redeclare var hello: typeof hello | boolean;
    currently there is no way to describe this with TypeScript

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Jun 1, 2020
@ghost
Copy link

ghost commented Jan 5, 2021

Came here for a mildly different reason, closer related to issue #14306, some variables were "declared" in the global scope, but I chose to explicitly make them into variables.

ex:

const { Map } = self;

const func  = () => {
    const variable = new Map{...};
    // Map cannot be overrode via devtools or such
}

Someone I was working with had actually noticed that global lookups were slowing down their code, so I tried to test this, but TS didn't like it.

@jcalz
Copy link
Contributor

jcalz commented Jan 16, 2022

cross-linking to #4062

@sdrsdr
Copy link

sdrsdr commented Oct 13, 2022

redeclare can have other benefits:

I have a "header" type of interface:

export interface api_msg_t {
	a:string;
	devid:string;
}

function is_api_msg(m:any) :m is api_msg_t {
	return (
		m && typeof(m)=="object" && typeof(m.a)=='string' && typeof(m.devid)=='string'
	)
}

I want to have extended interface:

interface api_node_online_t extends api_msg_t {
	a:"onl";
	nid:number;
	state:node_state;
}
function is_api_node_online(m:api_msg_t) : m is api_node_online_t {
	return (m.a=='onl' && typeof(m.nid)=="number" && is_node_state(m.state));
}

if there is no inex type in api_msg_t the typeguard is_api_node_online fails to transpile as m.nid does not exist in api_msg_t. But adding index in the api_msg_t propagates it to api_node_online_t that is undesirable. if we can do

function is_api_node_online(m:api_msg_t) : m is api_node_online_t {
	redaclare m as api_msg_t & {[index:string]:unknonw};
	return (m.a=='onl' && typeof(m.nid)=="number" && is_node_state(m.state));
}

It will allow for elegant and typesafe solution.

We can easily mimic this behavior with

interface api_msg_indexed_t extends api_msg_t {
	[index:string]:nknown;
}

function is_api_msg_indexed(m:api_msg_t):m is api_msg_indexed_t:{
	return true;
}

function is_api_node_online(m:api_msg_t) : m is api_node_online_t {
	if (!api_msg_indexed(m)) return;
	return (m.a=='onl' && typeof(m.nid)=="number" && is_node_state(m.state));
}

but this results in unnecessary JS code emitted and ran

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants