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

any objs without absorbs unions #46859

Closed
5 tasks done
mdbetancourt opened this issue Nov 18, 2021 · 6 comments
Closed
5 tasks done

any objs without absorbs unions #46859

mdbetancourt opened this issue Nov 18, 2021 · 6 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@mdbetancourt
Copy link

mdbetancourt commented Nov 18, 2021

πŸ” Search Terms

metadata, any object, any union

βœ… Viability Checklist

  • 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, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

a way to mark an object as "any" without lose all the know type information

πŸ“ƒ Motivating Example

some type as follow would be possible

const obj: unknown & { name: string } = getUser()
// or
const obj: AnyObject & { name: string } = getUser()
//instead of
const obj: { name: string; [k: string]: unknown } = getUser()
// add metadata to objects
type Throws<Err> = {
   [errorSymbol]: Err;
} & uknown
function connectDb(): Db | Throws<ConnectionError> {}

function getUsers(db: Db): User[] {}

getUsers(connectDb()) // currently error because Throws dont have Db properties
connectDb().connect // autocomplete and everything else works well

πŸ’» Use Cases

could be used to #46142

maybe related with this issue #46548 (dont need an additional type just a way to narrow instead of wide the union when any is present)

or typing some Proxies behaviors without lose type

type AnyObject = {
    [k: string]: never
    [i: number]: never
    [s: symbol]: never
}
type FlexibleObject = { lastGet: Date | null } &  AnyObject; // workaround allow autocomplete but...
function createFlexObject<T>(obj: T): T | FlexibleObject {
  obj['lastGet'] = null
  return new Proxy(
    obj,
    {
      get(target, prop) {
        target.lastGet = new Date()
        return prop in target ? target[prop] : '...'
      },
    }
  );
}
type User = { name: string }
const user: User = { name: '' }
const userWithLastGet = createFlexObject(user)

const targetUser: User = userWithLastGet
//     ^ error Property 'name' is missing in type 'FlexibleObject' but required in type 'User'

i'm using a workaround in https://www.npmjs.com/package/@kraftr/errors to add metadata error to the return

function couldThrow(): string | Throws<Error> & string {} i used a type to convert it to
function couldThrow(): Return<string, Error> {} but with this feature i can use
function couldThrow(): string | Throws<Error>{}
@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Nov 18, 2021
@RyanCavanaugh
Copy link
Member

It's very unclear to me what's being proposed here.

@mdbetancourt
Copy link
Author

mdbetancourt commented Nov 18, 2021

It's very unclear to me what's being proposed here.

check line 22 Playground "'connect' is declared here." even when i typed Throws as almost any

@mdbetancourt
Copy link
Author

@RyanCavanaugh i have another e.g

function defaultize<T>(value: T): Record<string, T> {
  return new Proxy({}, {
    get(target, prop) {
      if(target[prop] !== value) {
        target[prop] = value
      }
      return target[prop]
    }
  })
}

const { name } = defaultize('Jean') // currently with --noUncheckedIndexedAccess name is "string | undefined"

what is my idea?

function defaultize<T>(value: T): Record<string, T> & AnyObject {
....
}
const { name } = defaultize('Jean') // now is just "string" even with --noUncheckedIndexedAccess

@RyanCavanaugh RyanCavanaugh added Unactionable There isn't something we can do with this issue Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript and removed Needs More Info The issue still hasn't been fully clarified Unactionable There isn't something we can do with this issue labels Nov 22, 2021
@RyanCavanaugh
Copy link
Member

The concept of a JS object that appears to give itself any available property seems very rare in practice. I don't think this is something the type system needs to support.

@mdbetancourt
Copy link
Author

mdbetancourt commented Nov 23, 2021

i dont think so, is not so rare, i listed some uses cases:

  • objs with default values (like defaultdict in python)
  • metadata (e.g bring metadata about what errors a function could throw) function fn(): User | Throws<NotExists>
  • an option to allow any in an union without widening the original value e.g User | any result in any i know solutions could be User | User & Record<string, uknown> or User | User & { [k: string]: uknown } but it's awkward specially when there is more than just one type (User & Lead & Contact) | (User & Lead & Contact & Record<string, unknown>) or you just ending doing a type alias
  • i say could be related with [Feature request] Union type operator that prefer narrow types over wide onesΒ #46548 maybe { name: string } |& any could be a solution if that issue is aproved
  • collect variables names from destructuring, i'm creating a package to create cli in a more typed way:
const cli = createCLI()
cli.command('build', buildCmd => {
  const { config, root } = buildCmd.namedArg() // namedArg returns a Proxy with get trap to know the var name
  
  return () => {
    console.log(config.value)
    console.log(root.value)
  }
})
cli.parse(['xx', 'xx', 'build', '--config', 'config.ts', '--root', '.', '--watch']) // error watch is not a possible argument

or request handler with validation

// server.ts
constroller(() => {
  const { name, host } = urlQueries()
  return () => {
    // logic
  }
})
// client.ts
get('http://xxxx/users?name=xx&host=xx')

this last one could be helpful to some frameworks like vue to declare props e.g:
currently in vue 3 to declare props with setup we do

<script setup>
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  }
})
</script>

but one possible aproach when all props has the same type could be

<script setup>
const { modelValue } = defineProps(String, '')
</script>

@mdbetancourt
Copy link
Author

The concept of a JS object that appears to give itself any available property seems very rare in practice

@RyanCavanaugh Are you sure? that concept is precisely the proxy concept there is libs which could use this feature i think you closed this issue too fast there is a lot of uses cases check my previous comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

2 participants