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

Unable to use imported symbol as class property when exported inside an object #42830

Closed
kyeotic opened this issue Feb 17, 2021 · 12 comments
Closed

Comments

@kyeotic
Copy link

kyeotic commented Feb 17, 2021

Bug Report

imported symbols cannot be used as class properties. Symbols declared in the same file can.

πŸ”Ž Search Terms

  • Element implicitly has an 'any' type because type has no index signature
  • typescript symbol element no index signature

⏯ Playground Link

This playground example cannot recreate the bug because it depends in importing the symbol
Playground link with similar but slightly different code

πŸ’» Code

import {storeSymbols}  from './somewhere'

const _logger: symbol = storeSymbols._logger
const _dynamo: symbol = storeSymbols._dynamo

export class Cache {
  public [_dynamo]: ArcClient
  public [_logger]: Logger
  private metrics: CacheMetrics

  constructor({ dynamo, logger, metrics }: CacheConfig) {
    this[_logger] = wrapper(logger) // <--- errors
    this[_dynamo] = dynamo          // <--- errors
    this.metrics = metricsWrapper(metrics, this[_logger])
  }
}

πŸ™ Actual behavior

TS Error

Element implicitly has an 'any' type because type 'Cache' has no index signature. Did you mean to call 'set'?

πŸ™‚ Expected behavior

No errors

Here is the local error

image

Here is a screenshot with the error, next to the same code in the file that declared the symbol, where no error presents

image

@IllusionMH
Copy link
Contributor

IllusionMH commented Feb 17, 2021

Not sure about exports, but if you want to use symbol as key you need to use unique symbol . Example

@kyeotic
Copy link
Author

kyeotic commented Feb 17, 2021

Interesting. Why does it work when the symbol is created in the same file then? The type unique symbol is new to me, and doesn't appear in the signature for the Symbol() function.

What is the difference between a symbol (which mdn says is guaranteed to be unique) and a unique symbol? When is a symbol not a unique symbol?

Also, when I try to assign the symbol that is imported from the other file to a unique symbol I get a type error: Type 'symbol' is not assignable to type 'unique symbol'.ts(2322). Keep in mind, this value works in its source file and its type is symbol there. This problem only occurs when using an imported symbol.

@kyeotic
Copy link
Author

kyeotic commented Feb 17, 2021

Ok, this is definitely a bug. I found these docs that describe doing what I am doing:

// Lib
export const SERIALIZE = Symbol("serialize-method-key");

export interface Serializable {
  [SERIALIZE](obj: {}): string;
}
// consumer

import { SERIALIZE, Serializable } from "lib";

class JSONSerializableItem implements Serializable {
  [SERIALIZE](obj: {}) {
    return JSON.stringify(obj);
  }
}

The consumer is importing a symbol and using it as a class property. The only difference is that its a method, instead of a property that the constructor needs to assign a value to. It seems like the assignment is the issue, but how else can I set the value of this field?

I even tried pulling these properties out into a shared interface, in case that somehow impacts the "uniqueness" requirement here. Paradoxically it now complains about both:

// error 1, at class declaration
Class 'Cache' incorrectly implements interface 'DynamoStore'.
  Property '[_dynamo]' is missing in type 'Cache' but required in type 'DynamoStore'.ts(2420)
dynamo.ts(117, 3): '[_dynamo]' is declared here.

// error 2, at constructor property assignment for [_dynamo]
Element implicitly has an 'any' type because type 'Cache' has no index signature. Did you mean to call 'set'?

@kyeotic
Copy link
Author

kyeotic commented Feb 17, 2021

Nevermind, this is a duplicate of an apparently longstanding lack of support for symbol indexers.

Looks like I wont be using typescript for this library after all.

@kyeotic kyeotic closed this as completed Feb 17, 2021
@IllusionMH
Copy link
Contributor

I don't think that issue is relevant in you case.

Not going into quirks of symbols support you can look at symbol vs. unique symbol as string vs. 'some string'
any symbol assignable to symbol, but only one type is assignable to unique symbol. See https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol

You need to preserve this uniqueness of symbols but as you declare them as just symbol

const _dynamo: symbol = storeSymbols._dynamo`

it means any symbol.

Same error if you use string instead of string literal

@kyeotic
Copy link
Author

kyeotic commented Feb 17, 2021

@IllusionMH I tried declaring them as unique symbols and it didn't change the error. That issue seems to be directly relevant: typescript does not support user defined symbols as indexable properties.

If you have a potential solution for this I'm happy to try it out.

@IllusionMH
Copy link
Contributor

As said before - you need to make sure that they are unique.
Ideally ./somewhere should already have storeSymbols as {_logger: unique symbol, _dynamo: unique symbol} otherwise you need to use type assertion const _logger: unique symbol = storeSymbols._logger as any;

Example

@kyeotic
Copy link
Author

kyeotic commented Feb 17, 2021

I tried that. It doesn't work on the imported symbols. It works in the source file (the way the playground works) either way.

You should try opening an editor and importing a unique symbol. It doesn't work.

EDIT: ok... typescript is really picky about this.

This does not work, when importing the symbols lose their uniqueness

const _type = Symbol('_type')
const _dynamo: unique symbol = Symbol('_dynamo')
const _logger: unique symbol = Symbol('_logger')

export const storeSymbols = {
  _type,
  _dynamo,
  _logger,
}

But this works

const _dynamo: unique symbol = Symbol('_dynamo')
const _logger: unique symbol = Symbol('_logger')

export { _dynamo }
export { _logger }

@IllusionMH
Copy link
Contributor

IllusionMH commented Feb 17, 2021

UPD. Damn, GitHub didn't show your comment until I submitted this one.

OK I see - symbols in objects are still widened to symbol even with as const :(

However option with assertion still works.

ATM you have two options:

  1. perform assertions as in example
  2. export/import/use symbols directly (Cache2 on screenshot) as shown in docs you posted https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#constant-named-properties

image

@kyeotic
Copy link
Author

kyeotic commented Feb 17, 2021

Why does providing the unique symbol inside of an object cause it to lose its uniqueness? Aren't exports immutable bindings?

This is going to make it very awkward to structure the library exports.

@kyeotic kyeotic reopened this Feb 17, 2021
@kyeotic kyeotic changed the title Unable to use imported symbol as class property Unable to use imported symbol as class property when exported inside an object Feb 17, 2021
@IllusionMH
Copy link
Contributor

That's actually duplicate of #35562

@kyeotic kyeotic closed this as completed Feb 17, 2021
@kyeotic
Copy link
Author

kyeotic commented Feb 17, 2021

Yep, nice find.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants