mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-04 04:02:41 +00:00
Merge branch 'master' of github.com:Araq/Nimrod
This commit is contained in:
@@ -344,6 +344,12 @@ type
|
||||
tfFromGeneric, # type is an instantiation of a generic; this is needed
|
||||
# because for instantiations of objects, structural
|
||||
# type equality has to be used
|
||||
tfInstantiated # XXX: used to mark generic params after instantiation.
|
||||
# if the concrete type happens to be an implicit generic
|
||||
# this can lead to invalid proc signatures in the second
|
||||
# pass of semProcTypeNode performed after instantiation.
|
||||
# this won't be needed if we don't perform this redundant
|
||||
# second pass (stay tuned).
|
||||
tfAll, # type class requires all constraints to be met (default)
|
||||
tfAny, # type class requires any constraint to be met
|
||||
tfCapturesEnv, # whether proc really captures some environment
|
||||
|
||||
@@ -163,6 +163,7 @@ proc genConstStmt(p: BProc, t: PNode) =
|
||||
if it.kind == nkCommentStmt: continue
|
||||
if it.kind != nkConstDef: InternalError(t.info, "genConstStmt")
|
||||
var c = it.sons[0].sym
|
||||
if c.typ.containsCompileTimeOnly: continue
|
||||
if sfFakeConst in c.flags:
|
||||
genSingleVar(p, it)
|
||||
elif c.typ.kind in ConstantDataTypes and lfNoDecl notin c.loc.flags and
|
||||
|
||||
@@ -140,6 +140,14 @@ proc mangleName(s: PSym): PRope =
|
||||
proc isCompileTimeOnly(t: PType): bool =
|
||||
result = t.kind in {tyTypedesc, tyExpr}
|
||||
|
||||
proc containsCompileTimeOnly(t: PType): bool =
|
||||
if isCompileTimeOnly(t): return true
|
||||
if t.sons != nil:
|
||||
for i in 0 .. <t.sonsLen:
|
||||
if t.sons[i] != nil and isCompileTimeOnly(t.sons[i]):
|
||||
return true
|
||||
return false
|
||||
|
||||
var anonTypeName = toRope"TY"
|
||||
|
||||
proc typeName(typ: PType): PRope =
|
||||
@@ -174,7 +182,7 @@ proc mapType(typ: PType): TCTypeKind =
|
||||
of tyOpenArray, tyArrayConstr, tyArray, tyVarargs: result = ctArray
|
||||
of tyObject, tyTuple: result = ctStruct
|
||||
of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal,
|
||||
tyConst, tyMutable, tyIter, tyTypeDesc:
|
||||
tyConst, tyMutable, tyIter, tyTypeDesc:
|
||||
result = mapType(lastSon(typ))
|
||||
of tyEnum:
|
||||
if firstOrd(typ) < 0:
|
||||
|
||||
@@ -865,11 +865,11 @@ proc evalTypeTrait*(n: PNode, context: PSym): PNode =
|
||||
# by the type traits procs' signatures, but until the
|
||||
# code is more mature it doesn't hurt to be extra safe
|
||||
internalAssert n.sons.len >= 2 and n.sons[1].kind == nkSym
|
||||
|
||||
|
||||
let typ = n.sons[1].sym.typ.skipTypes({tyTypeDesc})
|
||||
case n.sons[0].sym.name.s.normalize
|
||||
of "name":
|
||||
result = newStrNode(nkStrLit, typ.typeToString(preferExported))
|
||||
result = newStrNode(nkStrLit, typ.typeToString(preferName))
|
||||
result.typ = newType(tyString, context)
|
||||
result.info = n.info
|
||||
else:
|
||||
@@ -965,7 +965,9 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
|
||||
of mParseExprToAst: result = evalParseExpr(c, n)
|
||||
of mParseStmtToAst: result = evalParseStmt(c, n)
|
||||
of mExpandToAst: result = evalExpandToAst(c, n)
|
||||
of mTypeTrait: result = evalTypeTrait(n, c.module)
|
||||
of mTypeTrait:
|
||||
n.sons[1] = evalAux(c, n.sons[1], {})
|
||||
result = evalTypeTrait(n, c.module)
|
||||
of mSlurp: result = evalSlurp(evalAux(c, n.sons[1], {}), c.module)
|
||||
of mStaticExec:
|
||||
let cmd = evalAux(c, n.sons[1], {})
|
||||
|
||||
@@ -28,7 +28,8 @@ proc equalGenericParams(procA, procB: PNode): bool =
|
||||
return
|
||||
a = procA.sons[i].sym
|
||||
b = procB.sons[i].sym
|
||||
if (a.name.id != b.name.id) or not sameTypeOrNil(a.typ, b.typ): return
|
||||
if (a.name.id != b.name.id) or
|
||||
not sameTypeOrNil(a.typ, b.typ, {TypeDescExactMatch}): return
|
||||
if (a.ast != nil) and (b.ast != nil):
|
||||
if not ExprStructuralEquivalent(a.ast, b.ast): return
|
||||
result = true
|
||||
|
||||
@@ -19,7 +19,8 @@ proc restoreOldStyleType(n: PNode) =
|
||||
#
|
||||
# This is strictly for backward compatibility until
|
||||
# the transition to types as first-class values is complete.
|
||||
n.typ = n.typ.skipTypes({tyTypeDesc})
|
||||
if n.typ.kind == tyTypeDesc and n.typ.sonsLen == 1:
|
||||
n.typ = n.typ.sons[0]
|
||||
|
||||
proc semTemplateExpr(c: PContext, n: PNode, s: PSym, semCheck = true): PNode =
|
||||
markUsed(n, s)
|
||||
@@ -376,6 +377,8 @@ proc semArrayConstr(c: PContext, n: PNode): PNode =
|
||||
|
||||
addSon(result, semExprWithType(c, x))
|
||||
var typ = skipTypes(result.sons[0].typ, {tyGenericInst, tyVar, tyOrdinal})
|
||||
# turn any concrete typedesc into the absract typedesc type
|
||||
if typ.kind == tyTypeDesc: typ.sons = nil
|
||||
for i in countup(1, sonsLen(n) - 1):
|
||||
x = n.sons[i]
|
||||
if x.kind == nkExprColonExpr and sonsLen(x) == 2:
|
||||
|
||||
@@ -33,6 +33,7 @@ proc instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable,
|
||||
#t = instGenericContainer(c, a, t)
|
||||
t = generateTypeInstance(c, pt, a, t)
|
||||
#t = ReplaceTypeVarsT(cl, t)
|
||||
t.flags.incl tfInstantiated
|
||||
s.typ = t
|
||||
addDecl(c, s)
|
||||
entry.concreteTypes[i] = t
|
||||
@@ -41,7 +42,8 @@ proc sameInstantiation(a, b: TInstantiatedSymbol): bool =
|
||||
if a.genericSym.id == b.genericSym.id and
|
||||
a.concreteTypes.len == b.concreteTypes.len:
|
||||
for i in 0 .. < a.concreteTypes.len:
|
||||
if not sameType(a.concreteTypes[i], b.concreteTypes[i]): return
|
||||
if not compareTypes(a.concreteTypes[i], b.concreteTypes[i],
|
||||
flags = {TypeDescExactMatch}): return
|
||||
result = true
|
||||
|
||||
proc GenericCacheGet(c: PContext, entry: var TInstantiatedSymbol): PSym =
|
||||
@@ -122,33 +124,6 @@ proc sideEffectsCheck(c: PContext, s: PSym) =
|
||||
s.ast.sons[genericParamsPos].kind == nkEmpty:
|
||||
c.threadEntries.add(s)
|
||||
|
||||
proc applyConcreteTypesToSig(genericProc: PSym, concTypes: seq[PType]): PType =
|
||||
# XXX: This is intended to replace the use of semParamList in generateInstance.
|
||||
# The results of semParamList's analysis are already encoded in the original
|
||||
# proc type and any concrete types may be aplied directly over it.
|
||||
# Besides being more efficient, it will remove the awkward case of
|
||||
# genericParams == nil in semParamList.
|
||||
# Currenly, it fails in some cases such as:
|
||||
# proc inc2*[T](x: var ordinal[T], y = 1) {.magic: "Inc", noSideEffect.}
|
||||
let sig = genericProc.typ
|
||||
result = copyType(sig, getCurrOwner(), false)
|
||||
result.n = sig.n.shallowCopy
|
||||
|
||||
for i in countup(0, sig.len - 1):
|
||||
let tOrig = sig.sons[i]
|
||||
if tOrig == nil: continue
|
||||
let oGenParams = genericProc.ast.sons[genericParamsPos]
|
||||
if skipTypes(tOrig, skipPtrs).kind in {tyGenericParam}:
|
||||
var tConcrete = concTypes[tOrig.sym.position]
|
||||
if i > 0:
|
||||
let param = sig.n.sons[i].sym.copySym
|
||||
param.typ = tConcrete
|
||||
result.n.sons[i] = newSymNode(param)
|
||||
result.sons[i] = tConcrete
|
||||
else:
|
||||
result.sons[i] = tOrig
|
||||
if i > 0: result.n.sons[i] = sig.n.sons[i]
|
||||
|
||||
proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
|
||||
info: TLineInfo): PSym =
|
||||
# no need to instantiate generic templates/macros:
|
||||
@@ -182,12 +157,8 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
|
||||
n.sons[genericParamsPos] = ast.emptyNode
|
||||
# semantic checking for the parameters:
|
||||
if n.sons[paramsPos].kind != nkEmpty:
|
||||
if false and nimdbg:
|
||||
result.typ = applyConcreteTypesToSig(fn, entry.concreteTypes)
|
||||
addParams(c, result.typ.n, fn.kind)
|
||||
else:
|
||||
removeDefaultParamValues(n.sons[ParamsPos])
|
||||
semParamList(c, n.sons[ParamsPos], nil, result)
|
||||
removeDefaultParamValues(n.sons[ParamsPos])
|
||||
semParamList(c, n.sons[ParamsPos], nil, result)
|
||||
else:
|
||||
result.typ = newTypeS(tyProc, c)
|
||||
rawAddSon(result.typ, nil)
|
||||
|
||||
@@ -34,10 +34,14 @@ proc semInstantiationInfo(c: PContext, n: PNode): PNode =
|
||||
proc semTypeTraits(c: PContext, n: PNode): PNode =
|
||||
checkMinSonsLen(n, 2)
|
||||
internalAssert n.sons[1].kind == nkSym
|
||||
if n.sons[1].sym.kind == skType:
|
||||
let typArg = n.sons[1].sym
|
||||
if typArg.kind == skType or
|
||||
(typArg.kind == skParam and typArg.typ.sonsLen > 0):
|
||||
# This is either a type known to sem or a typedesc
|
||||
# param to a regular proc (again, known at instantiation)
|
||||
result = evalTypeTrait(n, GetCurrOwner())
|
||||
else:
|
||||
# pass unmodified to evals
|
||||
# a typedesc variable, pass unmodified to evals
|
||||
result = n
|
||||
|
||||
proc semOrd(c: PContext, n: PNode): PNode =
|
||||
|
||||
@@ -552,7 +552,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
incl(result.flags, tfFinal)
|
||||
|
||||
proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) =
|
||||
if kind == skMacro:
|
||||
if kind == skMacro and param.typ.kind != tyTypeDesc:
|
||||
# within a macro, every param has the type PNimrodNode!
|
||||
# and param.typ.kind in {tyTypeDesc, tyExpr, tyStmt}:
|
||||
let nn = getSysSym"PNimrodNode"
|
||||
@@ -579,7 +579,8 @@ proc paramTypeClass(c: PContext, paramType: PType, procKind: TSymKind):
|
||||
result.typ = newTypeS(tyExpr, c)
|
||||
result.typ.sons = paramType.sons
|
||||
of tyTypeDesc:
|
||||
if procKind notin {skTemplate, skMacro}:
|
||||
if procKind notin {skTemplate, skMacro} and
|
||||
tfInstantiated notin paramType.flags:
|
||||
result.typ = newTypeS(tyTypeDesc, c)
|
||||
result.typ.sons = paramType.sons
|
||||
of tyDistinct:
|
||||
@@ -777,19 +778,12 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
else:
|
||||
result = instGenericContainer(c, n, result)
|
||||
|
||||
proc semTypeFromMacro(c: PContext, n: PNode): PType =
|
||||
# Expands a macro or template until a type is returned
|
||||
# results in an error type if the macro expands to something different
|
||||
var sym = expectMacroOrTemplateCall(c, n)
|
||||
markUsed(n, sym)
|
||||
case sym.kind
|
||||
of skMacro:
|
||||
result = semTypeNode(c, semMacroExpr(c, n, n, sym), nil)
|
||||
of skTemplate:
|
||||
result = semTypeNode(c, semTemplateExpr(c, n, sym), nil)
|
||||
proc semTypeExpr(c: PContext, n: PNode): PType =
|
||||
var n = semExprWithType(c, n)
|
||||
if n.kind == nkSym and n.sym.kind == skType:
|
||||
result = n.sym.typ
|
||||
else:
|
||||
LocalError(n.info, errXisNoMacroOrTemplate, n.renderTree)
|
||||
result = errorType(c)
|
||||
LocalError(n.info, errTypeExpected, n.renderTree)
|
||||
|
||||
proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = nil
|
||||
@@ -823,7 +817,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
result.addSonSkipIntLit(t2)
|
||||
result.flags.incl(if op.id == ord(wAnd): tfAll else: tfAny)
|
||||
else:
|
||||
result = semTypeFromMacro(c, n)
|
||||
result = semTypeExpr(c, n)
|
||||
of nkCurlyExpr:
|
||||
result = semTypeNode(c, n.sons[0], nil)
|
||||
if result != nil:
|
||||
|
||||
@@ -271,7 +271,7 @@ proc matchTypeClass(c: var TCandidate, typeClass, t: PType): TTypeRelation =
|
||||
of tyTypeClass:
|
||||
match = matchTypeClass(c, req, t) == isGeneric
|
||||
else: nil
|
||||
elif t.kind in {tyTypeDesc, tyObject}:
|
||||
elif t.kind in {tyObject}:
|
||||
match = sameType(t, req)
|
||||
|
||||
if tfAny in typeClass.flags:
|
||||
@@ -659,7 +659,8 @@ proc ParamTypesMatchAux(c: PContext, m: var TCandidate, f, a: PType,
|
||||
of isGeneric:
|
||||
inc(m.genericMatches)
|
||||
if m.calleeSym != nil and m.calleeSym.kind in {skMacro, skTemplate}:
|
||||
result = argOrig
|
||||
if f.kind == tyTypeDesc: result = arg
|
||||
else: result = argOrig
|
||||
else:
|
||||
result = copyTree(arg)
|
||||
result.typ = getInstantiatedType(c, arg, m, f)
|
||||
|
||||
@@ -585,10 +585,17 @@ type
|
||||
## or a == (distinct b)
|
||||
dcEqOrDistinctOf ## a equals b or a is distinct of b
|
||||
|
||||
TTypeCmpFlag* = enum
|
||||
IgnoreTupleFields,
|
||||
TypeDescExactMatch,
|
||||
AllowCommonBase
|
||||
|
||||
TTypeCmpFlags* = set[TTypeCmpFlag]
|
||||
|
||||
TSameTypeClosure = object {.pure.}
|
||||
cmp: TDistinctCompare
|
||||
ignoreTupleFields: bool
|
||||
recCheck: int
|
||||
flags: TTypeCmpFlags
|
||||
s: seq[tuple[a,b: int]] # seq for a set as it's hopefully faster
|
||||
# (few elements expected)
|
||||
|
||||
@@ -610,13 +617,14 @@ proc SameTypeOrNilAux(a, b: PType, c: var TSameTypeClosure): bool =
|
||||
if a == nil or b == nil: result = false
|
||||
else: result = SameTypeAux(a, b, c)
|
||||
|
||||
proc SameTypeOrNil*(a, b: PType): bool =
|
||||
proc SameTypeOrNil*(a, b: PType, flags: TTypeCmpFlags = {}): bool =
|
||||
if a == b:
|
||||
result = true
|
||||
else:
|
||||
if a == nil or b == nil: result = false
|
||||
else:
|
||||
var c = initSameTypeClosure()
|
||||
c.flags = flags
|
||||
result = SameTypeAux(a, b, c)
|
||||
|
||||
proc equalParam(a, b: PSym): TParamsEquality =
|
||||
@@ -655,7 +663,7 @@ proc equalParams(a, b: PNode): TParamsEquality =
|
||||
return paramsNotEqual # paramsIncompatible;
|
||||
# continue traversal! If not equal, we can return immediately; else
|
||||
# it stays incompatible
|
||||
if not SameTypeOrNil(a.sons[0].typ, b.sons[0].typ):
|
||||
if not SameTypeOrNil(a.sons[0].typ, b.sons[0].typ, {TypeDescExactMatch}):
|
||||
if (a.sons[0].typ == nil) or (b.sons[0].typ == nil):
|
||||
result = paramsNotEqual # one proc has a result, the other not is OK
|
||||
else:
|
||||
@@ -683,13 +691,13 @@ proc sameTuple(a, b: PType, c: var TSameTypeClosure): bool =
|
||||
for i in countup(0, sonsLen(a) - 1):
|
||||
var x = a.sons[i]
|
||||
var y = b.sons[i]
|
||||
if c.ignoreTupleFields:
|
||||
if IgnoreTupleFields in c.flags:
|
||||
x = skipTypes(x, {tyRange})
|
||||
y = skipTypes(y, {tyRange})
|
||||
|
||||
result = SameTypeAux(x, y, c)
|
||||
if not result: return
|
||||
if a.n != nil and b.n != nil and not c.ignoreTupleFields:
|
||||
if a.n != nil and b.n != nil and IgnoreTupleFields notin c.flags:
|
||||
for i in countup(0, sonsLen(a.n) - 1):
|
||||
# check field names:
|
||||
if a.n.sons[i].kind == nkSym and b.n.sons[i].kind == nkSym:
|
||||
@@ -760,7 +768,14 @@ proc sameObjectStructures(a, b: PType, c: var TSameTypeClosure): bool =
|
||||
if not SameObjectTree(a.n, b.n, c): return
|
||||
result = true
|
||||
|
||||
proc SameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
|
||||
proc sameChildrenAux(a, b: PType, c: var TSameTypeClosure): bool =
|
||||
if sonsLen(a) != sonsLen(b): return false
|
||||
result = true
|
||||
for i in countup(0, sonsLen(a) - 1):
|
||||
result = SameTypeOrNilAux(a.sons[i], b.sons[i], c)
|
||||
if not result: return
|
||||
|
||||
proc SameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
|
||||
template CycleCheck() =
|
||||
# believe it or not, the direct check for ``containsOrIncl(c, a, b)``
|
||||
# increases bootstrapping time from 2.4s to 3.3s on my laptop! So we cheat
|
||||
@@ -808,38 +823,43 @@ proc SameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
|
||||
CycleCheck()
|
||||
result = sameTuple(a, b, c)
|
||||
of tyGenericInst: result = sameTypeAux(lastSon(a), lastSon(b), c)
|
||||
of tyTypeDesc:
|
||||
if TypeDescExactMatch in c.flags:
|
||||
CycleCheck()
|
||||
result = sameChildrenAux(x, y, c)
|
||||
else:
|
||||
result = true
|
||||
of tyGenericParam, tyGenericInvokation, tyGenericBody, tySequence,
|
||||
tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyArrayConstr,
|
||||
tyArray, tyProc, tyConst, tyMutable, tyVarargs, tyIter,
|
||||
tyOrdinal, tyTypeDesc, tyTypeClass:
|
||||
if sonsLen(a) == sonsLen(b):
|
||||
CycleCheck()
|
||||
result = true
|
||||
for i in countup(0, sonsLen(a) - 1):
|
||||
result = SameTypeOrNilAux(a.sons[i], b.sons[i], c)
|
||||
if not result: return
|
||||
if result and (a.kind == tyProc):
|
||||
result = a.callConv == b.callConv
|
||||
tyOrdinal, tyTypeClass:
|
||||
CycleCheck()
|
||||
result = sameChildrenAux(a, b, c)
|
||||
if result and (a.kind == tyProc):
|
||||
result = a.callConv == b.callConv
|
||||
of tyRange:
|
||||
CycleCheck()
|
||||
CycleCheck()
|
||||
result = SameTypeOrNilAux(a.sons[0], b.sons[0], c) and
|
||||
SameValue(a.n.sons[0], b.n.sons[0]) and
|
||||
SameValue(a.n.sons[1], b.n.sons[1])
|
||||
of tyNone: result = false
|
||||
|
||||
proc SameType*(x, y: PType): bool =
|
||||
proc sameType*(x, y: PType): bool =
|
||||
var c = initSameTypeClosure()
|
||||
result = sameTypeAux(x, y, c)
|
||||
|
||||
|
||||
proc sameBackendType*(x, y: PType): bool =
|
||||
var c = initSameTypeClosure()
|
||||
c.ignoreTupleFields = true
|
||||
c.flags.incl IgnoreTupleFields
|
||||
result = sameTypeAux(x, y, c)
|
||||
|
||||
proc compareTypes*(x, y: PType, cmp: TDistinctCompare): bool =
|
||||
proc compareTypes*(x, y: PType,
|
||||
cmp: TDistinctCompare = dcEq,
|
||||
flags: TTypeCmpFlags = {}): bool =
|
||||
## compares two type for equality (modulo type distinction)
|
||||
var c = initSameTypeClosure()
|
||||
c.cmp = cmp
|
||||
c.flags = flags
|
||||
result = sameTypeAux(x, y, c)
|
||||
|
||||
proc inheritanceDiff*(a, b: PType): int =
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import unittest
|
||||
import unittest, typetraits
|
||||
|
||||
type
|
||||
TFoo[T, U] = object
|
||||
x: T
|
||||
y: U
|
||||
|
||||
proc getTypeName(t: typedesc): string = t.name
|
||||
|
||||
proc foo(T: typedesc[float], a: expr): string =
|
||||
result = "float " & $(a.len > 5)
|
||||
|
||||
@@ -21,6 +23,9 @@ template foo(T: typedesc[seq]): expr = "seq"
|
||||
test "types can be used as proc params":
|
||||
# XXX: `check` needs to know that TFoo[int, float] is a type and
|
||||
# cannot be assigned for a local variable for later inspection
|
||||
check ((string.getTypeName == "string"))
|
||||
check ((getTypeName(int) == "int"))
|
||||
|
||||
check ((foo(TFoo[int, float], 1000) == "TFoo 1000"))
|
||||
|
||||
var f = 10.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
discard """
|
||||
msg: "int\nstring\nTBar[int]"
|
||||
output: "int\nstring\nTBar[int]\nint\nrange 0..2"
|
||||
output: "int\nstring\nTBar[int]\nint\nrange 0..2\nstring"
|
||||
"""
|
||||
|
||||
import typetraits
|
||||
@@ -36,3 +36,24 @@ proc foo3[R, T](x: array[R, T]) =
|
||||
echo name(R)
|
||||
|
||||
foo3 arr
|
||||
|
||||
const TypeList = [int, string, seq[int]]
|
||||
|
||||
macro selectType(inType: typedesc): typedesc =
|
||||
var typeSeq = @[float, TBar[int]]
|
||||
|
||||
for t in TypeList:
|
||||
typeSeq.add(t)
|
||||
|
||||
typeSeq.add(inType)
|
||||
typeSeq.add(type(10))
|
||||
|
||||
var typeSeq2: seq[typedesc] = @[]
|
||||
typeSeq2 = typeSeq
|
||||
|
||||
result = typeSeq2[5]
|
||||
|
||||
var xvar: selectType(string)
|
||||
xvar = "proba"
|
||||
echo xvar.type.name
|
||||
|
||||
|
||||
Reference in New Issue
Block a user