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

Provide a way to alias namespaces #10187

Open
falsandtru opened this issue Aug 6, 2016 · 17 comments
Open

Provide a way to alias namespaces #10187

falsandtru opened this issue Aug 6, 2016 · 17 comments
Labels
Needs More Info The issue still hasn't been fully clarified Suggestion An idea for TypeScript

Comments

@falsandtru
Copy link
Contributor

falsandtru commented Aug 6, 2016

TypeScript should have a way to embed (type) namespaces. In the following case, assigned (embeded) namespace NS.A should have a C type.

TypeScript Version: master

Code

namespace NS_A {
  export class C {
  }
}
namespace NS {
  export var A = NS_A;
  export type A = NS_A;
}
var C = NS.A.C;
type C = NS.A.C;

Expected behavior:

$ node built/local/tsc.js --lib es6 -m commonjs --noEmit index.ts

Actual behavior:

$ node built/local/tsc.js --lib es6 -m commonjs --noEmit index.ts
index.ts(7,19): error TS2304: Cannot find name 'NS_A'.
index.ts(10,13): error TS2305: Module 'NS' has no exported member 'A'.
@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Aug 6, 2016
@RyanCavanaugh RyanCavanaugh changed the title Namespaces that assigned to types don't work with type namespace such as classes and interfaces Provide a way to alias namespaces Aug 6, 2016
@RyanCavanaugh
Copy link
Member

I've been thinking about this as well. We have var / const to alias a value, and type to alias a type, but no equivalent for namespace. We could even consider

namespace ns = foo;

for establishing namespace aliases.

@falsandtru
Copy link
Contributor Author

Thanks!! I agree its syntax.

related: #5220

@falsandtru
Copy link
Contributor Author

When extending namespaces, namespace merging is not good for immutability.

namespace ns = foo;
// foo is overwritten
namespace ns {
  export var bar;
}

Source namespaces shouldn't be changed by another module. So I think, an extends keyword for namespaces is a good solution.

// foo is not overwritten
namespace ns extends foo {
  export var bar;
}

@saschanaz
Copy link
Contributor

It seems this existing syntax just works?

namespace NS_A {
  export class C {
  }
}
namespace NS {
  export import A = NS_A;
}
var C = NS.A.C;
type C = NS.A.C;

@zpdDG4gta8XKpMCd
Copy link

why won't we unify objects and namespaces instead of making them even more separated without a good reason?

related #8358

@falsandtru
Copy link
Contributor Author

@saschanaz Oh, I didn't know its syntax. My problems were solved. Thanks for the great answer!!

@zpdDG4gta8XKpMCd
Copy link

as was shown it works fine as long as you separate values/functions from types, unifying them is what we all can benefit from

@RyanCavanaugh RyanCavanaugh added Needs More Info The issue still hasn't been fully clarified and removed In Discussion Not yet reached consensus labels Aug 22, 2016
@RyanCavanaugh
Copy link
Member

Looking to understand what the use cases for this are. Some real world code examples would be useful.

@falsandtru
Copy link
Contributor Author

My use case:

https://github.com/falsandtru/pjax-api/blob/v3.3.0/src/layer/domain/router/model/eav/entity.ts#L17

Near case:

https://github.com/falsandtru/localsocket/blob/v0.4.4/src/layer/domain/indexeddb/model/socket/data.ts#L18-L23

As a related case of #9102 :

class C {
}
namespace C {
    export type T = void;
}
class D extends C {}
import D = C; // error

@RyanCavanaugh
Copy link
Member

#11025 is also a good use case

@RyanCavanaugh
Copy link
Member

To explain what's going on in #11025 -- there's no way to alias in the namespace side of an elided module import into a merged identifier.

@bfricka
Copy link

bfricka commented Oct 17, 2018

@RyanCavanaugh I know this is pretty old, but I'm currently trying to create better type defs for the popular node-forge library, which does a bunch of aliasing. E.g.:

forge.pki = forge.pki || {};
module.exports = forge.pki.rsa = forge.rsa = forge.rsa || {};

Thus, I'm looking to do something where I can alias like:

declare module 'node-forge' {
  namespace pki {
    namespace rsa {
      interface GenerateKeyPairOptions {}
      generateKeyPair(opts?: GenerateKeyPairOptions): KeyPair;
    }
  }
  // What I'd like to do:
  // namespace rsa = pki.rsa;

  // What I've tried:
  const rsa = pki.rsa; // Access to values from namespace
  type rsa = pki.rsa; // Trying to alias as type to access types (doesn't work)
}

When using this lib, I should be able to do the following, but can't access types:

import {rsa} from 'node-forge';

// TS2503: Cannot find namespace 'rsa'
const opts: rsa.GenerateKeyPairOptions = {};

// Works fine as aliased value
rsa.generateKeyPair(); 

@hiranya911
Copy link

I have a similar use case that involves nested namespaces:

// index.d.ts
declare namespace Parent {

}

// other.d.ts
declare namespace Other {
  // exported members
}

I'd like to expose everything in Other as a nested namespace of Parent. Something like:

declare namespace Parent {
  namespace Child = Other;
}

Today I achieve this by individually re-exporting the members of Other:

// index.d.ts

import {Other} from './other';

declare namespace Parent {
  namespace Child {
    export import Member1 = Other.Member1;
    export import Member2 = Other.Member2;
    // and so on...
  }
}

@pgherveou
Copy link

stumbled upon this issue, it looks like this feature is supported now
see doc here https://www.typescriptlang.org/docs/handbook/namespaces.html#aliases
@falsandtru if that answer your question you might want to close this issue

@falsandtru
Copy link
Contributor Author

See #10187 (comment) and below.

@bakkot
Copy link
Contributor

bakkot commented Jul 30, 2022

@RyanCavanaugh I have another real-world use case, which is working around #48764 (comment).

Specifically, I have a library which has several classes which are gated behind an init function, like

export interface Foo {
  method(): void
};
interface FooCtor {
  new(): Foo
};

export function init(): { Foo: FooCtor } {
  class Foo { method() {} }
  return { Foo };
}

and as in the above example, the types for instances of the inner classes are exported from the library. The way you're supposed to use it is

import { init } from 'my-module';
import type * as MyMod from 'my-module';

let MyMod = init();
let foo: MyMod.Foo = new MyMod.Foo();

but this doesn't actually work, because the import type declaration conflicts with the let MyMod declaration.

I would like to suggest that users write

import type * as _MyMod from 'my-module';
namespace MyMod = _MyMod;

so that the MyMod binding appears only in the type domain and hence would not conflict with the binding of the same name in the value domain, but that isn't possible because there is no way to alias namespaces.

@ValeTheVioletMote
Copy link

Here's another use case - because of this problem ( #41567 #41513 ) we can't use import for types from a non-module js file without typescript giving a faulty/broken output. This is being called 'working as intended'. But because of this """feature""", we can no longer simply import a namespace via import NamespaceAlias from "namespace"

To be fair to the "working as intended" defenders, it is true that we aren't actually truly importing the value. This was actually a workaround to the underlying problem of being unable to alias a namespace.

In other words, this is no longer valid TS code while not in a module file:

import A from 'a';
type B = A.B;

The proposed workaround was to use import("a")

But this does not work

type A = import("a") // Module 'a' does not refer to a type, but is used as a type here. Did you mean 'typeof import('a')'?

Sure:

type A = typeof import("a")
type B = A.B // 'A' only refers to a type, but is being used as a namespace here.

Thus, there is a need for a namespace alias, otherwise in every single reference to this namespace I'm going to have to replace A. with import("a").

because this would, presumably, work fine

namespace A = import("a")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info The issue still hasn't been fully clarified Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

9 participants