reflect2

package module
Version: v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2018 License: Apache-2.0 Imports: 6 Imported by: 616

README

reflect2

Sourcegraph GoDoc Build Status codecov rcard License

reflect api that avoids runtime reflect.Value cost

  • reflect get/set interface{}, with type checking
  • reflect get/set unsafe.Pointer, without type checking
  • reflect2.TypeByName works like Class.forName found in java

json-iterator use this package to save runtime dispatching cost. This package is designed for low level libraries to optimize reflection performance. General application should still use reflect standard library.

reflect2.TypeByName

// given package is github.com/your/awesome-package
type MyStruct struct {
	// ...
}

// will return the type
reflect2.TypeByName("awesome-package.MyStruct")
// however, if the type has not been used
// it will be eliminated by compiler, so we can not get it in runtime

reflect2 get/set interface

valType := reflect2.TypeOf(1)
i := 1
j := 10
valType.Set(&i, &j)
// i will be 10

to get set type, always use its pointer *type

reflect2 get/set unsafe.Pointer

valType := reflect2.TypeOf(1)
i := 1
j := 10
valType.UnsafeSet(unsafe.Pointer(&i), unsafe.Pointer(&j))
// i will be 10

to get set type, always use its pointer *type

benchmark

Benchmark is not necessary for this package. It does nothing actually. As it is just a thin wrapper to make go runtime public. Both reflect2 and reflect call same function provided by runtime package exposed by go language.

unsafe safety

Instead of casting []byte to sliceHeader in your application using unsafe. We can use reflect2 instead. This way, if sliceHeader changes in the future, only reflect2 need to be upgraded.

reflect2 tries its best to keep the implementation same as reflect (by testing).

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ConfigSafe = Config{UseSafeImplementation: true}.Froze()
View Source
var ConfigUnsafe = Config{UseSafeImplementation: false}.Froze()

Functions

func IFaceToEFace

func IFaceToEFace(ptr unsafe.Pointer) interface{}

func IsNil

func IsNil(obj interface{}) bool

func IsNullable

func IsNullable(kind reflect.Kind) bool

func NoEscape

func NoEscape(p unsafe.Pointer) unsafe.Pointer

NoEscape hides a pointer from escape analysis. noescape is the identity function but escape analysis doesn't think the output depends on the input. noescape is inlined and currently compiles down to zero instructions. USE CAREFULLY!

func PtrOf

func PtrOf(obj interface{}) unsafe.Pointer

func RTypeOf

func RTypeOf(obj interface{}) uintptr

func UnsafeCastString

func UnsafeCastString(str string) []byte

Types

type API

type API interface {
	TypeOf(obj interface{}) Type
	Type2(type1 reflect.Type) Type
}

type ArrayType

type ArrayType interface {
	ListType
	Len() int
}

type Config

type Config struct {
	UseSafeImplementation bool
}

func (Config) Froze

func (cfg Config) Froze() *frozenConfig

type InterfaceType

type InterfaceType interface {
	NumMethod() int
}

type ListType

type ListType interface {
	Type
	Elem() Type
	SetIndex(obj interface{}, index int, elem interface{})
	UnsafeSetIndex(obj unsafe.Pointer, index int, elem unsafe.Pointer)
	GetIndex(obj interface{}, index int) interface{}
	UnsafeGetIndex(obj unsafe.Pointer, index int) unsafe.Pointer
}

type MapIterator

type MapIterator interface {
	HasNext() bool
	Next() (key interface{}, elem interface{})
	UnsafeNext() (key unsafe.Pointer, elem unsafe.Pointer)
}

type MapType

type MapType interface {
	Type
	Key() Type
	Elem() Type
	MakeMap(cap int) interface{}
	UnsafeMakeMap(cap int) unsafe.Pointer
	SetIndex(obj interface{}, key interface{}, elem interface{})
	UnsafeSetIndex(obj unsafe.Pointer, key unsafe.Pointer, elem unsafe.Pointer)
	TryGetIndex(obj interface{}, key interface{}) (interface{}, bool)
	GetIndex(obj interface{}, key interface{}) interface{}
	UnsafeGetIndex(obj unsafe.Pointer, key unsafe.Pointer) unsafe.Pointer
	Iterate(obj interface{}) MapIterator
	UnsafeIterate(obj unsafe.Pointer) MapIterator
}

type PtrType

type PtrType interface {
	Type
	Elem() Type
}

func TypeOfPtr

func TypeOfPtr(obj interface{}) PtrType

type SliceType

type SliceType interface {
	ListType
	MakeSlice(length int, cap int) interface{}
	UnsafeMakeSlice(length int, cap int) unsafe.Pointer
	Grow(obj interface{}, newLength int)
	UnsafeGrow(ptr unsafe.Pointer, newLength int)
	Append(obj interface{}, elem interface{})
	UnsafeAppend(obj unsafe.Pointer, elem unsafe.Pointer)
	LengthOf(obj interface{}) int
	UnsafeLengthOf(ptr unsafe.Pointer) int
	SetNil(obj interface{})
	UnsafeSetNil(ptr unsafe.Pointer)
	Cap(obj interface{}) int
	UnsafeCap(ptr unsafe.Pointer) int
}

type StructField

type StructField interface {
	Offset() uintptr
	Name() string
	PkgPath() string
	Type() Type
	Tag() reflect.StructTag
	Index() []int
	Anonymous() bool
	Set(obj interface{}, value interface{})
	UnsafeSet(obj unsafe.Pointer, value unsafe.Pointer)
	Get(obj interface{}) interface{}
	UnsafeGet(obj unsafe.Pointer) unsafe.Pointer
}

type StructType

type StructType interface {
	Type
	NumField() int
	Field(i int) StructField
	FieldByName(name string) StructField
	FieldByIndex(index []int) StructField
	FieldByNameFunc(match func(string) bool) StructField
}

type Type

type Type interface {
	Kind() reflect.Kind
	// New return pointer to data of this type
	New() interface{}
	// UnsafeNew return the allocated space pointed by unsafe.Pointer
	UnsafeNew() unsafe.Pointer
	// PackEFace cast a unsafe pointer to object represented pointer
	PackEFace(ptr unsafe.Pointer) interface{}
	// Indirect dereference object represented pointer to this type
	Indirect(obj interface{}) interface{}
	// UnsafeIndirect dereference pointer to this type
	UnsafeIndirect(ptr unsafe.Pointer) interface{}
	// Type1 returns reflect.Type
	Type1() reflect.Type
	Implements(thatType Type) bool
	String() string
	RType() uintptr
	// interface{} of this type has pointer like behavior
	LikePtr() bool
	IsNullable() bool
	IsNil(obj interface{}) bool
	UnsafeIsNil(ptr unsafe.Pointer) bool
	Set(obj interface{}, val interface{})
	UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer)
	AssignableTo(anotherType Type) bool
}

func DefaultTypeOfKind

func DefaultTypeOfKind(kind reflect.Kind) Type

DefaultTypeOfKind return the non aliased default type for the kind

func PtrTo

func PtrTo(typ Type) Type

func Type2

func Type2(type1 reflect.Type) Type

func TypeByName

func TypeByName(typeName string) Type

TypeByName return the type by its name, just like Class.forName in java

func TypeByPackageName

func TypeByPackageName(pkgPath string, name string) Type

TypeByPackageName return the type by its package and name

func TypeOf

func TypeOf(obj interface{}) Type

type UnsafeArrayType

type UnsafeArrayType struct {
	// contains filtered or unexported fields
}

func (*UnsafeArrayType) GetIndex

func (type2 *UnsafeArrayType) GetIndex(obj interface{}, index int) interface{}

func (*UnsafeArrayType) Indirect

func (type2 *UnsafeArrayType) Indirect(obj interface{}) interface{}

func (*UnsafeArrayType) IsNil

func (type2 *UnsafeArrayType) IsNil(obj interface{}) bool

func (*UnsafeArrayType) LikePtr

func (type2 *UnsafeArrayType) LikePtr() bool

func (*UnsafeArrayType) New

func (type2 *UnsafeArrayType) New() interface{}

func (*UnsafeArrayType) PackEFace

func (type2 *UnsafeArrayType) PackEFace(ptr unsafe.Pointer) interface{}

func (*UnsafeArrayType) RType

func (type2 *UnsafeArrayType) RType() uintptr

func (*UnsafeArrayType) Set

func (type2 *UnsafeArrayType) Set(obj interface{}, val interface{})

func (*UnsafeArrayType) SetIndex

func (type2 *UnsafeArrayType) SetIndex(obj interface{}, index int, elem interface{})

func (*UnsafeArrayType) UnsafeGetIndex

func (type2 *UnsafeArrayType) UnsafeGetIndex(obj unsafe.Pointer, index int) unsafe.Pointer

func (*UnsafeArrayType) UnsafeIndirect

func (type2 *UnsafeArrayType) UnsafeIndirect(ptr unsafe.Pointer) interface{}

func (*UnsafeArrayType) UnsafeIsNil

func (type2 *UnsafeArrayType) UnsafeIsNil(ptr unsafe.Pointer) bool

func (*UnsafeArrayType) UnsafeNew

func (type2 *UnsafeArrayType) UnsafeNew() unsafe.Pointer

func (*UnsafeArrayType) UnsafeSet

func (type2 *UnsafeArrayType) UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer)

func (*UnsafeArrayType) UnsafeSetIndex

func (type2 *UnsafeArrayType) UnsafeSetIndex(obj unsafe.Pointer, index int, elem unsafe.Pointer)

type UnsafeEFaceType

type UnsafeEFaceType struct {
	// contains filtered or unexported fields
}

func (*UnsafeEFaceType) Indirect

func (type2 *UnsafeEFaceType) Indirect(obj interface{}) interface{}

func (*UnsafeEFaceType) IsNil

func (type2 *UnsafeEFaceType) IsNil(obj interface{}) bool

func (*UnsafeEFaceType) LikePtr

func (type2 *UnsafeEFaceType) LikePtr() bool

func (*UnsafeEFaceType) New

func (type2 *UnsafeEFaceType) New() interface{}

func (*UnsafeEFaceType) PackEFace

func (type2 *UnsafeEFaceType) PackEFace(ptr unsafe.Pointer) interface{}

func (*UnsafeEFaceType) RType

func (type2 *UnsafeEFaceType) RType() uintptr

func (*UnsafeEFaceType) Set

func (type2 *UnsafeEFaceType) Set(obj interface{}, val interface{})

func (*UnsafeEFaceType) UnsafeIndirect

func (type2 *UnsafeEFaceType) UnsafeIndirect(ptr unsafe.Pointer) interface{}

func (*UnsafeEFaceType) UnsafeIsNil

func (type2 *UnsafeEFaceType) UnsafeIsNil(ptr unsafe.Pointer) bool

func (*UnsafeEFaceType) UnsafeNew

func (type2 *UnsafeEFaceType) UnsafeNew() unsafe.Pointer

func (*UnsafeEFaceType) UnsafeSet

func (type2 *UnsafeEFaceType) UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer)

type UnsafeIFaceType

type UnsafeIFaceType struct {
	// contains filtered or unexported fields
}

func (*UnsafeIFaceType) Indirect

func (type2 *UnsafeIFaceType) Indirect(obj interface{}) interface{}

func (*UnsafeIFaceType) IsNil

func (type2 *UnsafeIFaceType) IsNil(obj interface{}) bool

func (*UnsafeIFaceType) LikePtr

func (type2 *UnsafeIFaceType) LikePtr() bool

func (*UnsafeIFaceType) New

func (type2 *UnsafeIFaceType) New() interface{}

func (*UnsafeIFaceType) PackEFace

func (type2 *UnsafeIFaceType) PackEFace(ptr unsafe.Pointer) interface{}

func (*UnsafeIFaceType) RType

func (type2 *UnsafeIFaceType) RType() uintptr

func (*UnsafeIFaceType) Set

func (type2 *UnsafeIFaceType) Set(obj interface{}, val interface{})

func (*UnsafeIFaceType) UnsafeIndirect

func (type2 *UnsafeIFaceType) UnsafeIndirect(ptr unsafe.Pointer) interface{}

func (*UnsafeIFaceType) UnsafeIsNil

func (type2 *UnsafeIFaceType) UnsafeIsNil(ptr unsafe.Pointer) bool

func (*UnsafeIFaceType) UnsafeNew

func (type2 *UnsafeIFaceType) UnsafeNew() unsafe.Pointer

func (*UnsafeIFaceType) UnsafeSet

func (type2 *UnsafeIFaceType) UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer)

type UnsafeMapIterator

type UnsafeMapIterator struct {
	// contains filtered or unexported fields
}

func (*UnsafeMapIterator) HasNext

func (iter *UnsafeMapIterator) HasNext() bool

func (*UnsafeMapIterator) Next

func (iter *UnsafeMapIterator) Next() (interface{}, interface{})

func (*UnsafeMapIterator) UnsafeNext

func (iter *UnsafeMapIterator) UnsafeNext() (unsafe.Pointer, unsafe.Pointer)

type UnsafeMapType

type UnsafeMapType struct {
	// contains filtered or unexported fields
}

func (*UnsafeMapType) GetIndex

func (type2 *UnsafeMapType) GetIndex(obj interface{}, key interface{}) interface{}

func (*UnsafeMapType) Indirect

func (type2 *UnsafeMapType) Indirect(obj interface{}) interface{}

func (*UnsafeMapType) IsNil

func (type2 *UnsafeMapType) IsNil(obj interface{}) bool

func (*UnsafeMapType) Iterate

func (type2 *UnsafeMapType) Iterate(obj interface{}) MapIterator

func (*UnsafeMapType) Key

func (type2 *UnsafeMapType) Key() Type

func (*UnsafeMapType) LikePtr

func (type2 *UnsafeMapType) LikePtr() bool

func (*UnsafeMapType) MakeMap

func (type2 *UnsafeMapType) MakeMap(cap int) interface{}

func (*UnsafeMapType) New

func (type2 *UnsafeMapType) New() interface{}

func (*UnsafeMapType) PackEFace

func (type2 *UnsafeMapType) PackEFace(ptr unsafe.Pointer) interface{}

func (*UnsafeMapType) RType

func (type2 *UnsafeMapType) RType() uintptr

func (*UnsafeMapType) Set

func (type2 *UnsafeMapType) Set(obj interface{}, val interface{})

func (*UnsafeMapType) SetIndex

func (type2 *UnsafeMapType) SetIndex(obj interface{}, key interface{}, elem interface{})

func (*UnsafeMapType) TryGetIndex

func (type2 *UnsafeMapType) TryGetIndex(obj interface{}, key interface{}) (interface{}, bool)

func (*UnsafeMapType) UnsafeGetIndex

func (type2 *UnsafeMapType) UnsafeGetIndex(obj unsafe.Pointer, key unsafe.Pointer) unsafe.Pointer

func (*UnsafeMapType) UnsafeIndirect

func (type2 *UnsafeMapType) UnsafeIndirect(ptr unsafe.Pointer) interface{}

func (*UnsafeMapType) UnsafeIsNil

func (type2 *UnsafeMapType) UnsafeIsNil(ptr unsafe.Pointer) bool

func (*UnsafeMapType) UnsafeIterate

func (type2 *UnsafeMapType) UnsafeIterate(obj unsafe.Pointer) MapIterator

func (*UnsafeMapType) UnsafeMakeMap

func (type2 *UnsafeMapType) UnsafeMakeMap(cap int) unsafe.Pointer

func (*UnsafeMapType) UnsafeNew

func (type2 *UnsafeMapType) UnsafeNew() unsafe.Pointer

func (*UnsafeMapType) UnsafeSet

func (type2 *UnsafeMapType) UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer)

func (*UnsafeMapType) UnsafeSetIndex

func (type2 *UnsafeMapType) UnsafeSetIndex(obj unsafe.Pointer, key unsafe.Pointer, elem unsafe.Pointer)

type UnsafePtrType

type UnsafePtrType struct {
	// contains filtered or unexported fields
}

func (*UnsafePtrType) Indirect

func (type2 *UnsafePtrType) Indirect(obj interface{}) interface{}

func (*UnsafePtrType) IsNil

func (type2 *UnsafePtrType) IsNil(obj interface{}) bool

func (*UnsafePtrType) LikePtr

func (type2 *UnsafePtrType) LikePtr() bool

func (*UnsafePtrType) New

func (type2 *UnsafePtrType) New() interface{}

func (*UnsafePtrType) PackEFace

func (type2 *UnsafePtrType) PackEFace(ptr unsafe.Pointer) interface{}

func (*UnsafePtrType) RType

func (type2 *UnsafePtrType) RType() uintptr

func (*UnsafePtrType) Set

func (type2 *UnsafePtrType) Set(obj interface{}, val interface{})

func (*UnsafePtrType) UnsafeIndirect

func (type2 *UnsafePtrType) UnsafeIndirect(ptr unsafe.Pointer) interface{}

func (*UnsafePtrType) UnsafeIsNil

func (type2 *UnsafePtrType) UnsafeIsNil(ptr unsafe.Pointer) bool

func (*UnsafePtrType) UnsafeNew

func (type2 *UnsafePtrType) UnsafeNew() unsafe.Pointer

func (*UnsafePtrType) UnsafeSet

func (type2 *UnsafePtrType) UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer)

type UnsafeSliceType

type UnsafeSliceType struct {
	// contains filtered or unexported fields
}

func (*UnsafeSliceType) Append

func (type2 *UnsafeSliceType) Append(obj interface{}, elem interface{})

func (*UnsafeSliceType) Cap

func (type2 *UnsafeSliceType) Cap(obj interface{}) int

func (*UnsafeSliceType) GetIndex

func (type2 *UnsafeSliceType) GetIndex(obj interface{}, index int) interface{}

func (*UnsafeSliceType) Grow

func (type2 *UnsafeSliceType) Grow(obj interface{}, newLength int)

func (*UnsafeSliceType) Indirect

func (type2 *UnsafeSliceType) Indirect(obj interface{}) interface{}

func (*UnsafeSliceType) IsNil

func (type2 *UnsafeSliceType) IsNil(obj interface{}) bool

func (*UnsafeSliceType) LengthOf

func (type2 *UnsafeSliceType) LengthOf(obj interface{}) int

func (*UnsafeSliceType) LikePtr

func (type2 *UnsafeSliceType) LikePtr() bool

func (*UnsafeSliceType) MakeSlice

func (type2 *UnsafeSliceType) MakeSlice(length int, cap int) interface{}

func (*UnsafeSliceType) New

func (type2 *UnsafeSliceType) New() interface{}

func (*UnsafeSliceType) PackEFace

func (type2 *UnsafeSliceType) PackEFace(ptr unsafe.Pointer) interface{}

func (*UnsafeSliceType) RType

func (type2 *UnsafeSliceType) RType() uintptr

func (*UnsafeSliceType) Set

func (type2 *UnsafeSliceType) Set(obj interface{}, val interface{})

func (*UnsafeSliceType) SetIndex

func (type2 *UnsafeSliceType) SetIndex(obj interface{}, index int, elem interface{})

func (*UnsafeSliceType) SetNil

func (type2 *UnsafeSliceType) SetNil(obj interface{})

func (*UnsafeSliceType) UnsafeAppend

func (type2 *UnsafeSliceType) UnsafeAppend(obj unsafe.Pointer, elem unsafe.Pointer)

func (*UnsafeSliceType) UnsafeCap

func (type2 *UnsafeSliceType) UnsafeCap(ptr unsafe.Pointer) int

func (*UnsafeSliceType) UnsafeGetIndex

func (type2 *UnsafeSliceType) UnsafeGetIndex(obj unsafe.Pointer, index int) unsafe.Pointer

func (*UnsafeSliceType) UnsafeGrow

func (type2 *UnsafeSliceType) UnsafeGrow(obj unsafe.Pointer, newLength int)

func (*UnsafeSliceType) UnsafeIndirect

func (type2 *UnsafeSliceType) UnsafeIndirect(obj unsafe.Pointer) interface{}

func (*UnsafeSliceType) UnsafeIsNil

func (type2 *UnsafeSliceType) UnsafeIsNil(ptr unsafe.Pointer) bool

func (*UnsafeSliceType) UnsafeLengthOf

func (type2 *UnsafeSliceType) UnsafeLengthOf(obj unsafe.Pointer) int

func (*UnsafeSliceType) UnsafeMakeSlice

func (type2 *UnsafeSliceType) UnsafeMakeSlice(length int, cap int) unsafe.Pointer

func (*UnsafeSliceType) UnsafeNew

func (type2 *UnsafeSliceType) UnsafeNew() unsafe.Pointer

func (*UnsafeSliceType) UnsafeSet

func (type2 *UnsafeSliceType) UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer)

func (*UnsafeSliceType) UnsafeSetIndex

func (type2 *UnsafeSliceType) UnsafeSetIndex(obj unsafe.Pointer, index int, elem unsafe.Pointer)

func (*UnsafeSliceType) UnsafeSetNil

func (type2 *UnsafeSliceType) UnsafeSetNil(ptr unsafe.Pointer)

type UnsafeStructField

type UnsafeStructField struct {
	reflect.StructField
	// contains filtered or unexported fields
}

func (*UnsafeStructField) Anonymous

func (field *UnsafeStructField) Anonymous() bool

func (*UnsafeStructField) Get

func (field *UnsafeStructField) Get(obj interface{}) interface{}

func (*UnsafeStructField) Index

func (field *UnsafeStructField) Index() []int

func (*UnsafeStructField) Name

func (field *UnsafeStructField) Name() string

func (*UnsafeStructField) Offset

func (field *UnsafeStructField) Offset() uintptr

func (*UnsafeStructField) PkgPath

func (field *UnsafeStructField) PkgPath() string

func (*UnsafeStructField) Set

func (field *UnsafeStructField) Set(obj interface{}, value interface{})

func (*UnsafeStructField) Tag

func (field *UnsafeStructField) Tag() reflect.StructTag

func (*UnsafeStructField) Type

func (field *UnsafeStructField) Type() Type

func (*UnsafeStructField) UnsafeGet

func (field *UnsafeStructField) UnsafeGet(obj unsafe.Pointer) unsafe.Pointer

func (*UnsafeStructField) UnsafeSet

func (field *UnsafeStructField) UnsafeSet(obj unsafe.Pointer, value unsafe.Pointer)

type UnsafeStructType

type UnsafeStructType struct {
	// contains filtered or unexported fields
}

func (*UnsafeStructType) Field

func (type2 *UnsafeStructType) Field(i int) StructField

func (*UnsafeStructType) FieldByIndex

func (type2 *UnsafeStructType) FieldByIndex(index []int) StructField

func (*UnsafeStructType) FieldByName

func (type2 *UnsafeStructType) FieldByName(name string) StructField

func (*UnsafeStructType) FieldByNameFunc

func (type2 *UnsafeStructType) FieldByNameFunc(match func(string) bool) StructField

func (*UnsafeStructType) Indirect

func (type2 *UnsafeStructType) Indirect(obj interface{}) interface{}

func (*UnsafeStructType) IsNil

func (type2 *UnsafeStructType) IsNil(obj interface{}) bool

func (*UnsafeStructType) LikePtr

func (type2 *UnsafeStructType) LikePtr() bool

func (*UnsafeStructType) New

func (type2 *UnsafeStructType) New() interface{}

func (*UnsafeStructType) PackEFace

func (type2 *UnsafeStructType) PackEFace(ptr unsafe.Pointer) interface{}

func (*UnsafeStructType) RType

func (type2 *UnsafeStructType) RType() uintptr

func (*UnsafeStructType) Set

func (type2 *UnsafeStructType) Set(obj interface{}, val interface{})

func (*UnsafeStructType) UnsafeIndirect

func (type2 *UnsafeStructType) UnsafeIndirect(ptr unsafe.Pointer) interface{}

func (*UnsafeStructType) UnsafeIsNil

func (type2 *UnsafeStructType) UnsafeIsNil(ptr unsafe.Pointer) bool

func (*UnsafeStructType) UnsafeNew

func (type2 *UnsafeStructType) UnsafeNew() unsafe.Pointer

func (*UnsafeStructType) UnsafeSet

func (type2 *UnsafeStructType) UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL